<?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[Open Machine Learning Course - Medium]]></title>
        <description><![CDATA[A series of articles on basics of Machine Learning. Each article is followed by an assignment with a deadline. Several Kaggle Inclass competitions are held throughout the course. - Medium]]></description>
        <link>https://medium.com/open-machine-learning-course?source=rss----b76595fb6cfc---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Open Machine Learning Course - Medium</title>
            <link>https://medium.com/open-machine-learning-course?source=rss----b76595fb6cfc---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 07:33:05 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/open-machine-learning-course" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 10. Gradient Boosting]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-10-gradient-boosting-c751538131ac?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/c751538131ac</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[gradient-boosting]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[education]]></category>
            <category><![CDATA[mathematics]]></category>
            <dc:creator><![CDATA[Anastasia Manokhina]]></dc:creator>
            <pubDate>Mon, 16 Apr 2018 09:08:57 GMT</pubDate>
            <atom:updated>2018-09-19T09:13:45.709Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*0iWnfFGk7gOHlVJv.jpg" /></figure><p>Hello everyone!</p><p>So far, we’ve covered 9 topics from Exploratory Data Analysis to Time Series Analysis in Python. Today we are going to have a look at one of the most popular and practical machine learning algorithms: gradient boosting. You can find more detailed math in the <a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic10_boosting/topic10_gradient_boosting.ipynb?flush_cache=true">nbviewer</a> format of the article.</p><h3>Article Outline</h3><ol><li>Introduction and history of boosting</li><li>Gradient Boosting Machine algorithm</li><li>Loss functions</li><li>Assignment #10</li><li>Useful resources</li></ol><h3>1. Introduction and history of boosting</h3><p>Almost everyone in machine learning has heard about gradient boosting. Many data scientists include this algorithm in their data scientist’s toolbox because of the good results it yields on any given (unknown) problem.</p><p>Furthermore, XGBoost is often the standard recipe for <a href="https://github.com/dmlc/xgboost/blob/master/demo/README.md#usecases">winning</a> <a href="http://blog.kaggle.com/tag/xgboost/">ML competitions</a>. It is so popular that the idea of stacking XGBoosts has become a meme. Moreover, boosting is an important component in <a href="https://en.wikipedia.org/wiki/Learning_to_rank#Practical_usage_by_search_engines">many recommender systems</a>; sometimes, it is even considered a <a href="https://yandex.com/company/technologies/matrixnet/">brand</a>. Let’s look at the history and development of boosting.</p><p>Boosting was born out of <a href="http://www.cis.upenn.edu/%7Emkearns/papers/boostnote.pdf">the question:</a> is it possible to get one strong model from a large amount of relatively weak and simple models?<br> By saying “weak models”, we do not mean simple basic models like decision trees but models with poor accuracy performance, where poor is a little bit better than random.</p><p><a href="http://www.cs.princeton.edu/%7Eschapire/papers/strengthofweak.pdf">A positive mathematical answer</a> to this question was identified, but it took a few years to develop fully functioning algorithms based on this solution e.g. AdaBoost. These algoritms take a greedy approach: first, they build a linear combination of simple models (basic algorithms) by re-weighing the input data. Then, the model (usually a decision tree) is built on earlier incorrectly predicted objects, which are now given larger weights.</p><p>Many machine learning courses study AdaBoost — the ancestor of GBM (Gradient Boosting Machine). However, since AdaBoost merged with GBM, it has become apparent that AdaBoost is just a particular variation of GBM.</p><p>The algorithm itself has a very clear visual interpretation and intuition for defining weights. Let’s have a look at the following toy classification problem where we are going to split the data between the trees of depth 1 (also known as ‘stumps’) on each iteration of AdaBoost. For the first two iterations, we have the following picture:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*FXGsWdsC_ucq-5E6.jpg" /></figure><p>The size of point corresponds to its weight, which was assigned for an incorrect prediction. On each iteration, we can see that these weights are growing — the stumps cannot cope with this problem. Although, if we take a weighted vote for the stumps, we will get the correct classifications:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*WOo4d8oNmb85y_Eb.png" /></figure><p>Here is a more detailed example of AdaBoost where, as we iterate, we can see the weights increase, especially on the border between classes.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fk4G2VCuOMMg%3Ffeature%3Doembed&amp;url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dk4G2VCuOMMg&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fk4G2VCuOMMg%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/b0ff0cb9ca93927cef56bf70f1c754ce/href">https://medium.com/media/b0ff0cb9ca93927cef56bf70f1c754ce/href</a></iframe><p>AdaBoost works well, but <a href="https://www.cs.princeton.edu/courses/archive/spring07/cos424/papers/boosting-survey.pdf">the lack</a> of explanation for why the algorithm is successful sewed the seeds of doubt. Some considered it a super-algorithm, a silver bullet, but others were skeptical and believed AdaBoost was just overfitting.</p><p>The overfitting problem did indeed exist, especially when data had strong outliers. Therefore, in those types of problems, AdaBoost was unstable. Fortunately, a few professors in the statistics department at Stanford, who had created Lasso, Elastic Net, and Random Forest, started researching the algorithm. In 1999, Jerome Friedman came up with the generalization of boosting algorithms development — Gradient Boosting (Machine), also known as GBM. With this work, Friedman set up the statistical foundation for many algorithms providing the general approach of boosting for optimization in the functional space.</p><p>CART, bootstrap, and many other algorithms have originated from Stanford’s statistics department. In doing so, the department has solidified their names in future textbooks. These algorithms are very practical, and some recent works have yet to be widely adopted. For example, check out <a href="https://arxiv.org/abs/1308.2719">glinternet</a>.</p><p>Not many video recordings of Friedman are available. Although, there is a very interesting <a href="https://www.youtube.com/watch?v=8hupHmBVvb0">interview</a> with him about the creation of CART and how they solved statistics problems (which is similar to data analysis and data science today) more than 40 years ago. There is also a great <a href="https://www.youtube.com/watch?v=zBk3PK3g-Fc">lecture from Hastie</a>, a retrospective on data analysis from one of the creators of methods that we use everyday.</p><p>In general, there has been a transition from engineering and algorithmic research to a full-fledged approach to building and studying algorithms. From a mathematical perspective, this is not a big change — we are still adding (or boosting) weak algorithms and enlarging our ensemble with gradual improvements for parts of the data where the model was inaccurate. But, this time, the next simple model is not just built on re-weighted objects but improves its approximation of the gradient of overall objective function. This concept greatly opens up our algorithms for imagination and extensions.</p><h3>History of GBM</h3><p>It took more than 10 years after the introduction of GBM for it to become an essential part of the data science toolbox. GBM was extended to apply to different statistics problems: GLMboost and GAMboost for strengthening already existing GAM models, CoxBoost for survival curves, and RankBoost and LambdaMART for ranking.</p><p>Many realizations of GBM also appeared under different names and on different platforms: Stochastic GBM, GBDT (Gradient Boosted Decision Trees), GBRT (Gradient Boosted Regression Trees), MART (Multiple Additive Regression Trees), and more. In addition, the ML community was very segmented and dissociated, which made it hard to track just how widespread boosting had become.</p><p>At the same time, boosting had been actively used in search ranking. This problem was rewritten in terms of a loss function that penalizes errors in the output order, so it became convenient to simply insert it into GBM. AltaVista was one of the first companies who introduced boosting to ranking. Soon, the ideas spread to Yahoo, Yandex, Bing, etc. Once this happened, boosting became one of the main algorithms that was used not only in research but also in core technologies in industry.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/563/0*an9iD6SIRXuPZmLX.jpg" /></figure><p>ML competitions, especially Kaggle, played a major role in boosting’s popularization. Now, researchers had a common platform where they could compete in different data science problems with large number of participants from around the world. With Kaggle, one could test new algorithms on the real data, giving algoritms oppurtunity to “shine”, and provide full information in sharing model performance results across competition data sets. This is exactly what happened to boosting when it was used at <a href="http://blog.kaggle.com/2011/12/21/score-xavier-conort-on-coming-second-in-give-me-some-credit/">Kaggle</a> (check interviews with Kaggle winners starting from 2011 who mostly used boosting). The <a href="https://github.com/dmlc/xgboost">XGBoost</a> library quickly gained popularity after its appearance. XGBoost is not a new, unique algorithm; it is just an extremely effective realization of classic GBM with additional heuristics.</p><p>This algorithm has gone through very typical path for ML algorithms today: mathematical problem and algorithmic crafts to successful practical applications and mass adoption years after its first appearance.</p><h3>2. Gradient Boosting Machine algorithm</h3><p>We are going to solve the problem of function approximation in a general supervised learning setting. We have a set of features <em>X</em> and target variables <em>y</em> which we use to restore the dependence <em>y = f(x)</em>. We restore the dependence by approximating <em>f(x)</em> and by understanding which approximation is better when we use the loss function<em> L(y,f)</em> , which we want to minimize:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/584/1*tdpcch5rQCsZqJJeUh45pQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*otjjv305ztb4FrjxMtJwGg.jpeg" /></figure><p>At this moment, we do not make any assumptions regarding the type of dependence<em> f(x)</em>, the model of our approximation, or the distribution of the target variable. We only expect that the function <em>L(y, f)</em> is differentiable. Our formula is very general; let’s define it for a particular data set with a population mean. Our expression for minimizing the loss of the data is the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/486/1*OjYlFgZ1tfTCDWNJum3wZA.png" /></figure><p>Unfortunately, the number of these functions is not just large, its functional space is infinite-dimensional. That is why it is acceptable for us to limit the search space by some family of functions. This simplifies the objective a lot because now we have a solvable optimization of parameter values.</p><p>Simple analytical solutions for finding the optimal parameters often do not exist, so the parameters are usually approximated iteratively. To start, we write down the empirical loss function that will allow us to evaluate our parameters using our data. Additionally, let’s write out our approximation for a number of <em>M</em> iterations as a sum:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/445/1*xhIPLMpWFI-0RqQd_l48fw.png" /></figure><p>Then, the only thing left is to find a suitable, iterative algorithm to minimize the last expression. Gradient descent is the simplest and most frequently used option. We define the gradient and add our iterative evaluations to it (since we are minimizing the loss, we add the minus sign). Our last step is to initialize our first approximation and choose the number of iterations <em>M</em>. Let’s review the steps for this inefficient and naive algorithm:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/987/1*Q5iyNlz2j4YUdiCs8NYP6Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*oNRjI8whWPwBirtz.jpg" /></figure><h3>Functional gradient descent</h3><p>Let’s imagine for a second that we can perform optimization in the function space and iteratively search for the approximations as functions themselves. We will express our approximation as a sum of incremental improvements, each being a function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/279/1*OIOmMWQxiXd-pc8wXa6NjA.png" /></figure><p>Nothing has happened yet; we have only decided that we will search for our approximation not as a big model with plenty of parameters (as an example, neural network), but as a sum of functions, pretending we move in functional space.</p><p>In order to accomplish this task, we need to limit our search by some function family. There are a few issues here — first of all, the sum of models can be more complicated than any model from this family; secondly, the general objective is still in functional space. Let’s note that, on every step, we will need to select an optimal coefficient. For step <em>t</em>, the problem is the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/763/1*eoNsGlMX7H4GasAF2oVM_Q.png" /></figure><p>Here is where the magic happens. We have defined all of our objectives in general terms, as if we could have trained any kind of model for any type of loss functions. In practice, this is extremely difficult, but, fortunately, there is a simple way to solve this task.</p><p>Knowing the expression of loss function’s gradient, we can calculate its value on our data. So, let’s train the models such that our predictions will be more correlated with this gradient (with a minus sign). In other words, we will use least squares to correct the predictions with these residuals. For classification, regression, and ranking tasks, we will minimize the squared difference between pseudo-residuals <em>r</em> and our predictions. For step <em>t</em>, the final problem looks the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/709/1*hLIKdYIXOwjRZLR_O50-mA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/620/1*Cfl-KIvZGJDpY5T2rdGn9w.jpeg" /></figure><h3>Friedman’s classic GBM algorithm</h3><p>We can now define the classic GBM algorithm suggested by Jerome Friedman in 1999. It is a supervised algorithm that has the following components:</p><ul><li>a dataset;</li><li>number of iterations <em>M</em>;</li><li>choice of loss function with a defined gradient;</li><li>choice of function family of base algorithms with the training procedure;</li><li>additional hyperparameters (for example, in decision trees, the tree depth);</li></ul><p>The only thing left is the initial approximation. For simplicity, for an initial approximation, a constant value <em>gamma</em> is used. The constant value, as well as the optimal coefficient, are identified via binary search or another line search algorithm over the initial loss function (not a gradient). So, we have our GBM algorithm described as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*upXdtzPiXWb6qxu0kf9vTA.png" /></figure><h3>Step-By-Step example: How GBM Works</h3><p>Let’s see an example of how GBM works. In this toy example, we will restore a noisy function</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/662/1*DDd038gBGnhT1wU4sSPlWw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*rGPIxjECjNOpjxkr.jpg" /></figure><p>This is a regression problem with a real-valued target, so we will choose to use the mean squared error loss function. We will generate 300 pairs of observations and approximate them with decision trees of depth 2. Let’s put together everything we need to use GBM:</p><ul><li>Toy data</li><li>Number of iterations <em>M</em> = 3</li><li>The mean squared error loss function <em>L(y, f) = (y - f)²</em></li><li>Gradient of <em>L(y, f) </em>is just residuals <em>r = (y-f)</em></li><li>Decision trees as base algorithms</li><li>Hyperparameters of the decision trees: trees depth is equal to 2</li></ul><p>We will run GBM and draw two types of graphs: the current approximation (blue graph) and every tree built on its pseudo-residuals (green graph). The graph’s number corresponds to the iteration number:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*ey5DTLQEqjErvFPb.png" /></figure><p>By the second iteration, our trees have recovered the basic form of the function. However, at the first iteration, we see that the algorithm has built only the “left branch” of the function. This was due to the fact that our trees simply did not have enough depth to build a symmetrical branch at once, and it focused on the left branch with the larger error. Therefore, the right branch appeared only after the second iteration.</p><p>The rest of the process goes as expected — on every step, our pseudo-residuals decreased, and GBM approximated the original function better and better with each iteration. However, by construction, trees cannot approximate a continuous function, which means that GBM is not ideal in this example. To play with GBM function approximations, you can use the awesome interactive demo in this blog called <a href="http://arogozhnikov.github.io/2016/06/24/gradient_boosting_explained.html">Brilliantly wrong</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hD-rrBuYl7YGsjHK.jpg" /></figure><h3>3. Loss functions</h3><p>If we want to solve a classification problem instead of regression, what would change? We only need to choose a suitable loss function <em>L(y, f)</em>. This is the most important, high-level moment that determines exactly how we will optimize and what characteristics we can expect in the final model.</p><p>As a rule, we do not need to invent this ourselves — researchers have already done it for us. Today, we will explore loss functions for the two most common objectives: regression and binary classification.</p><h3>Regression loss functions</h3><p>Let’s start with a regression problem for <em>y, </em>a real number. In order to choose the appropriate loss function, we need to consider which of the properties of the conditional distribution <em>(y|x)</em> we want to restore. The most common options are:</p><ul><li><em>L(y, f) = (y — f)²</em> a.k.a. <em>L2</em> loss or Gaussian loss. It is the classical conditional mean, which is the simplest and most common case. If we do not have any additional information or requirements for a model to be robust, we can use the Gaussian loss.</li><li><em>L(y, f) = |y — f|</em> a.k.a. <em>L1</em> loss or Laplacian loss. At the first glance, this function does not seem to be differentiable, but it actually defines the conditional median. Median, as we know, is robust to outliers, which is why this loss function is better in some cases. The penalty for big variations is not as heavy as it is in <em>L2</em>.</li><li><em>Lq</em> loss or Quantile loss. Instead of median, it uses quantiles. We can see that this function is asymmetric and penalizes the observations which are on the right side of the defined quantile.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*USweEiQSTDgr0R3k.png" /></figure><p>Let’s use loss function <em>L_q</em> on our data. The goal is to restore the conditional 75%-quantile of cosine. Let us put everything together for GBM:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/907/1*QQEOhhXzCSY332IZwiOpzQ.png" /></figure><p>For our initial approximation, we will take the needed quantile of <em>y</em>. However, we do not know anything about optimal coefficients, so we’ll use standard line search. The results are the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*AowHruj1-q9dk-f4.png" /></figure><p>The overall results of GBM with quantile loss function are the same as the results with quadratic loss function offset by ~ 0.135. But if we were to use the 90%-quantile, we would not have enough data due to the fact that classes would become unbalanced. We need to remember this when we deal with non-standard problems.</p><p>For regression tasks, many loss functions have been developed, some of them with extra properties. For example, they can be robust like in the <a href="https://en.wikipedia.org/wiki/Huber_loss">Huber loss function</a>. For a small number of outliers, the loss function works as <em>L2</em>, but after a defined threshold, the function changes to <em>L1</em>. This allows for decreasing the effect of outliers and focusing on the overall picture.</p><p>We can illustrate this with the following example. Data is generated from the function <em>y = sin(x) / x</em> with added noise, a mixture from normal and Bernoulli distributions. We show the functions on graphs A-D and the relevant GBM on F-H (graph E represents the initial function):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Ik3NLHThFTXzh1S8.jpg" /></figure><p><a href="https://habrastorage.org/web/130/05b/222/13005b222e8a4eb68c3936216c05e276.jpg">Original size</a>.</p><p>In this example, we used splines as the base algorithm. See, it does not always have to be trees for boosting?</p><p>We can clearly see the difference between the functions <em>L2</em>, <em>L1</em>, and Huber loss. If we choose optimal parameters for the Huber loss, we can get the best possible approximation among all our options. The difference can be seen as well in the 10%, 50%, and 90%-quantiles.</p><p>Unfortunately, Huber loss function is supported only by very few popular libraries/packages; h2o supports it, but XGBoost does not. It is relevant to other things that are more exotic like <a href="https://www.slideshare.net/charthur/quantile-and-expectile-regression">conditional expectiles</a>, but it may still be interesting knowledge.</p><h3>Classification loss functions</h3><p>Now, let’s look at the binary classification problem. We saw that GBM can even optimize non-differentiable loss functions. Technically, it is possible to solve this problem with a regression <em>L2</em> loss, but it wouldn’t be correct.</p><p>The distribution of the target variable requires us to use log-likehood, so we need to have different loss functions for targets multiplied by their predictions. The most common choices would be the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/896/1*8Hpqips1Cp7FsqdY4sh1mg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*PyqMgyOg-fGTTKex.png" /></figure><p>Let’s generate some new toy data for our classification problem. As a basis, we will take our noisy cosine, and we will use the sign function for classes of the target variable. Our toy data looks like the following (jitter-noise is added for clarity):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*Zd6vKIjtg2K2pnla.jpg" /></figure><p>We will use logistic loss to look for what we actually boost. So, again, we put together what we will use for GBM:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*n5N1gyHRZpqyA0_zCwxLWg.png" /></figure><p>This time, the initialization of the algorithm is a little bit harder. First, our classes are imbalanced (63% versus 37%). Second, there is no known analytical formula for the initialization of our loss function, so we have to look for it via search:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*IwKMsEDfi4J1SMm0.png" /></figure><p>Our optimal initial approximation is around -0.273. You could have guessed that it was negative because it is more profitable to predict everything as the most popular class, but there is no formula for the exact value. Now let’s finally start GBM, and look what actually happens under the hood:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*iEKgSshKSFGZ4B6u.png" /></figure><p>The algorithm successfully restored the separation between our classes. You can see how the “lower” areas are separating because the trees are more confident in the correct prediction of the negative class and how the two steps of mixed classes are forming. It is clear that we have a lot of correctly classified observations and some amount of observations with large errors that appeared due to the noise in the data.</p><h3>Weights</h3><p>Sometimes, there is a situation where we want a more specific loss function for our problem. For example, in financial time series, we may want to give bigger weight to large movements in the time series; for churn prediction, it is more useful to predict the churn of clients with high LTV (or lifetime value: how much money a client will bring in the future).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/778/0*vkvEjJa9lKKDA8FJ.jpg" /></figure><p>The statistical warrior would invent their own loss function, write out the gradient for it (for more effective training, include the Hessian), and carefully check whether this function satisfies the required properties. However, there is a high probability of making a mistake somewhere, running up against computational difficulties, and spending an inordinate amount of time on research.</p><p>In lieu of this, a very simple instrument was invented (which is rarely remembered in practice): weighing observations and assigning weight functions. The simplest example of such weighting is the setting of weights for class balance. In general, if we know that some subset of data, both in the input variables and in the target variable, has greater importance for our model, then we just assign them a larger weight <em>w(x,y)</em>. The main goal is to fulfill the general requirements for weights:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/432/1*bF6AY5U5WT7tudI1-DuGMg.png" /></figure><p>Weights can significantly reduce the time spent adjusting the loss function for the task we are solving and also encourages experiments with the target models’ properties. Assigning these weights is entirely a function of creativity. We simply add scalar weights:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/509/1*pQrKBoXOTQPkkzarDbZ2hQ.png" /></figure><p>It is clear that, for arbitrary weights, we do not know the statistical properties of our model. Often, linking the weights to the values <em>y</em> can be too complicated. For example, the usage of weights proportional to <em>|y|</em> in <em>L1</em> loss function is not equivalent to <em>L2</em> loss because the gradient will not take into account the values of the predictions themselves.</p><p>We mention all of this so that we can understand our possibilities better. Let’s create some very exotic weights for our toy data. We will define a strongly asymmetric weight function as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/496/1*Ga5zBDi-ulT_vMFbnS4Srw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*S4Aa6HtQQ8zu7w1c.png" /></figure><p>With these weights, we expect to get two properties: less detailing for negative values of <em>X</em> and the form of the function, similar to the initial cosine. We take the other GBM’s tunings from our previous example with classification including the line search for optimal coefficients. Let’s look what we’ve got:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/0*9Iy53Xc64NUsCIKL.png" /></figure><p>We achieved the result that we expected. First, we can see how strongly the pseudo-residuals differ; on the initial iteration, they look almost like the original cosine. Second, the left part of the function’s graph was often ignored in favor of the right one, which had larger weights. Third, the function that we got on the third iteration received enough attention and started looking similar to the original cosine (also started to slightly overfit).</p><p>Weights are a powerful but risky tool that we can use to control the properties of our model. If you want to optimize your loss function, it is worth trying to solve a more simple problem first but add weights to the observations at your discretion.</p><h3>Conclusion</h3><p>Today, we learned the theory behind gradient boosting. GBM is not just some specific algorithm but a common methodology for building ensembles of models. In addition, this methodology is sufficiently flexible and expandable — it is possible to train a large number of models, taking into consideration different loss-functions with a variety of weighting functions.</p><p>Practice and ML competitions show that, in standard problems (except for image, audio, and very sparse data), GBM is often the most effective algorithm (not to mention stacking and high-level ensembles, where GBM is almost always a part of them). Also, there are many adaptations of GBM <a href="https://arxiv.org/abs/1603.04119">for Reinforcement Learning</a> (Minecraft, ICML 2016). By the way, the Viola-Jones algorithm, which is still used in computer vision, <a href="https://en.wikipedia.org/wiki/Viola%E2%80%93Jones_object_detection_framework#Learning_algorithm">is based on AdaBoost</a>.</p><p>In this article, we intentionally omitted questions concerning GBM’s regularization, stochasticity, and hyper-parameters. It was not accidental that we used a small number of iterations <em>M</em> = 3 throughout. If we used 30 trees instead of 3 and trained the GBM as described, the result would not be that predictable:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mAgF4tebZmeQjVG7G2PCOg.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b0NHYi3_w6UOgynUvEM2zw.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ebVIcma7f3iM6Dqe.jpg" /><figcaption><a href="http://arogozhnikov.github.io/2016/07/05/gradient_boosting_playground.html">http://arogozhnikov.github.io/2016/07/05/gradient_boosting_playground.html</a></figcaption></figure><h3>4. Assignment #10</h3><p>Full versions of assignments are announced each week in a new run of the course (October 1, 2018). Meanwhile, you can practice with a demo version: <a href="https://www.kaggle.com/kashnitsky/assignment-10-gradient-boosting-and-flight-delays">Kaggle Kernel</a>, <a href="https://mlcourse.ai/notebooks/blob/master/jupyter_english/assignments_demo/assignment10_flight_delays_kaggle.ipynb?flush_cache=true">nbviewer</a>.</p><h3>5. Useful resources</h3><ul><li><a href="https://statweb.stanford.edu/~jhf/ftp/trebst.pdf">Original article</a> about GBM by Jerome Friedman</li><li>“Gradient boosting machines, a tutorial”, <a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3885826/">paper</a> by Alexey Natekin, and Alois Knoll</li><li><a href="http://statweb.stanford.edu/~tibs/ElemStatLearn/printings/ESLII_print10.pdf">Chapter in Elements of Statistical Learning</a> from Hastie, Tibshirani, Friedman (page 337)</li><li><a href="https://en.wikipedia.org/wiki/Gradient_boosting">Wiki</a> article about Gradient Boosting</li><li><a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3885826/">Frontiers tutorial</a> article about GBM</li><li><a href="https://www.youtube.com/watch?v=wPqtzj5VZus">Video-lecture by Hastie</a> about GBM at h2o.ai conference</li></ul><p><em>Author: </em><a href="https://www.linkedin.com/in/natekin/"><em>Alexey Natekin</em></a><em>, OpenDataScience founder, Machine Learning Evangelist. Translated and edited by </em><a href="https://www.linkedin.com/in/odaykhovskaya/"><em>Olga Daykhovskaya</em></a><em>, </em><a href="https://www.linkedin.com/in/anastasiamanokhina/"><em>Anastasia Manokhina</em></a><em>, </em><a href="https://www.linkedin.com/in/egor-polusmak/"><em>Egor Polusmak</em></a><em>, and </em><a href="https://www.linkedin.com/in/yuanyuanpao/"><em>Yuanyuan Pao</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c751538131ac" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-10-gradient-boosting-c751538131ac">Open Machine Learning Course. Topic 10. Gradient Boosting</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 9. Part 2. Predicting the future with Facebook Prophet]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-9-part-3-predicting-the-future-with-facebook-prophet-3f3af145cdc?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/3f3af145cdc</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[education]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[timeseries]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Egor Polusmak]]></dc:creator>
            <pubDate>Sat, 14 Apr 2018 12:27:03 GMT</pubDate>
            <atom:updated>2018-09-19T09:16:53.360Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*uJ0iEDIY5b-zzsFMKYK14Q.png" /></figure><p>Time series forecasting finds wide application in data analytics. These are only some of the conceivable predictions of future trends that might be useful:</p><ul><li>The number of servers that an online service will need next year;</li><li>The demand for a grocery product at a supermarket on a given day;</li><li>The tomorrow closing price of a tradable financial asset.</li></ul><p>For another example, we can make a prediction of some team’s performance and then use it as a baseline: first to set goals for the team, and then to measure the actual team performance relative to the baseline.</p><p>There are quite a few different methods to predict future trends, for example, <a href="https://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average">ARIMA</a>, <a href="https://en.wikipedia.org/wiki/Autoregressive_conditional_heteroskedasticity">ARCH</a>, <a href="https://en.wikipedia.org/wiki/Autoregressive_model">regressive models</a>, <a href="https://medium.com/machine-learning-world/neural-networks-for-algorithmic-trading-1-2-correct-time-series-forecasting-backtesting-9776bfd9e589">neural networks</a>.</p><p>In this article, we will look at <a href="https://facebook.github.io/prophet/">Prophet</a>, a library for time series forecasting released by Facebook and open-sourced on February 23, 2017. We will also try it out in the problem of predicting the daily number of posts published on Medium.</p><h3>Article outline</h3><p>1. Introduction</p><p>2. The Prophet Forecasting Model</p><p>3. Practice with Prophet</p><ul><li>3.1 Installation in Python</li><li>3.2 Dataset</li><li>3.3 Exploratory visual analysis</li><li>3.4 Making a forecast</li><li>3.5 Forecast quality evaluation</li><li>3.6 Visualization</li></ul><p>4. Box-Cox Transformation</p><p>5. Summary</p><p>6. References</p><p><strong>The following material is better viewed as a </strong><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic09_time_series/topic9_part2_facebook_prophet.ipynb"><strong>Jupyter notebook</strong></a><strong> and can be reproduced locally with Jupyter if you clone the </strong><a href="https://github.com/Yorko/mlcourse_open"><strong>course repository</strong></a><strong>.</strong></p><h3>1. Introduction</h3><p>According to the <a href="https://research.fb.com/prophet-forecasting-at-scale/">article</a> by Facebook Research, Prophet was initially developed for the purpose of creating high quality business forecasts. This library tries to address the following difficulties common to many business time series:</p><ul><li>Seasonal effects caused by human behavior: weekly, monthly and yearly cycles, dips and peaks on public holidays;</li><li>Changes in trend due to new products and market events;</li><li>Outliers.</li></ul><p>The authors claim that, even with default settings, in many cases, their library produces forecasts as accurate as those delivered by experienced analysts.</p><p>Moreover, Prophet has a number of intuitive and easily interpretable customizations that allow gradually improving the quality of the forecasting model. What is especially important, these parameters are quite comprehensible even for non-experts in time series analysis, which is a field of data science requiring certain skill and experience.</p><p>By the way, the original article is called “Forecasting at Scale”, but it is not about the scale in the “usual” sense, that it’s addressing computational and infrastructure problems of a large number of working programs. According to the authors, Prophet should scale well in the following 3 areas:</p><ul><li>Accessibility to a wide audience of analysts, possibly without profound expertise in time series;</li><li>Applicability to a wide range of distinct forecasting problems;</li><li>Automated performance estimation of a large number of forecasts including flagging of potential problems for their subsequent inspection by the analyst.</li></ul><h3>2. The Prophet Forecasting Model</h3><p>Now, let’s take a closer look at how Prophet works. In its essence, this library utilizes the <a href="https://en.wikipedia.org/wiki/Additive_model">additive regression model</a> <em>y(t)</em> comprising the following components:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/200/1*R6TNOoREENKjbznhZdrGIw.png" /></figure><p>where:</p><ul><li>Trend <em>g(t)</em> models non-periodic changes;</li><li>Seasonality <em>s(t) </em>represents periodic changes;</li><li>Holidays component <em>h(t)</em> contributes information about holidays and events.</li></ul><p>Below, we will consider some important properties of these model components.</p><h4>Trend</h4><p>The Prophet library implements two possible trend models for <em>g(t)</em>.</p><p>The first one is called <em>Nonlinear, Saturating Growth</em>. It is represented in the form of the <a href="https://en.wikipedia.org/wiki/Logistic_function">logistic growth model</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/146/1*egEuwH9DTF_xa78AkRLEtg.png" /></figure><p>where:</p><ul><li><em>C</em> is the carrying capacity (that is the curve’s maximum value).</li><li><em>k</em> is the growth rate (which represents “the steepness” of the curve).</li><li><em>m</em> is an offset parameter.</li></ul><p>This logistic equation allows modelling non-linear growth with saturation, that is when the growth rate of a value decreases with its growth. One of the typical examples would be representing the growth of the audience of an application or a website.</p><p>Actually, CC and kk are not necessarily constants and may vary over time. Prophet supports both automatic and manual tuning of their variability. The library can itself choose optimal points of trend changes by fitting the supplied historical data.</p><p>Also, Prophet allows analysts to manually set changepoints of the growth rate and capacity values at different points in time. For instance, analysts may have insights about dates of past releases that prominently influenced some key product indicators.</p><p>The second trend model is a simple <em>Piecewise Linear Model</em> with a constant rate of growth. It is best suited for problems without saturating growth.</p><h4>Seasonality</h4><p>The seasonal component <em>s(t)</em> provides a flexible model of periodic changes due to weekly and yearly seasonality.</p><p>Weekly seasonal data is modeled with dummy variables. Six new variables are added: monday, tuesday, wednesday, thursday, friday, saturday, which take values 0 or 1 depending on the day of the week. The feature sunday is not added because it would be a linear combination of the other days of the week, and this fact would have an adverse effect on the model.</p><p>Yearly seasonality model in Prophet relies on Fourier series.</p><p>Since <a href="https://github.com/facebook/prophet">version 0.2</a> you can also use <em>sub-daily time series</em> and make <em>sub-daily forecasts</em> as well as employ the new <em>daily seasonality</em> feature.</p><h4>Holidays and Events</h4><p>The component <em>h(t)</em> represents predictable abnormal days of the year including those on irregular schedules, e.g., Black Fridays.</p><p>To utilize this feature, the analyst needs to provide a custom list of events.</p><h4>Error</h4><p>The error term <em>ϵ(t)</em> represents information that was not reflected in the model. Usually it is modeled as normally distributed noise.</p><h4>Prophet Benchmarking</h4><p>For a detailed description of the model and algorithms behind Prophet refer to the paper <a href="https://peerj.com/preprints/3190/">“Forecasting at scale”</a> by Sean J. Taylor and Benjamin Letham.</p><p>The authors also compared their library with several other methods for time series forecasting. They used <a href="https://en.wikipedia.org/wiki/Mean_absolute_percentage_error">Mean Absolute Percentage Error (MAPE)</a> as a measure of prediction accuracy. In this research, Prophet has shown substantially lower forecasting error than the other models.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/460/1*9GMssxQ7h3acrQQdgQkJiw.png" /></figure><p>Let’s look closer at how the forcasting quality was measured in the article. To do this, we will need the formula of Mean Absolute Percentage Error.</p><p>Let <em>yi</em> be the <em>actual (historical) value</em> and <em>ŷi</em> be the <em>forecast value</em> given by our model.</p><p>Then <em>ei=yi−ŷi</em> is the <em>forecast error</em> and pi=ei/yi is the <em>relative forecast error</em>.</p><p>We define</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/154/1*IqH6qXcz_kHq_QGP46OLZg.png" /></figure><p>MAPE is widely used as a measure of prediction accuracy because it expresses error as a percentage and thus can be used in model evaluations on different datasets.</p><p>In addition, when evaluating a forecasting algorithm, it may prove useful to calculate <a href="https://en.wikipedia.org/wiki/Mean_absolute_error">MAE (Mean Absolute Error)</a> in order to have a picture of errors in absolute numbers. Using previously defined components, its equation will be</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/145/1*RV7box5i6ZGOnbugJjuyOA.png" /></figure><p>A few words about the algorithms that Prophet was compared with. Most of them are quite simple and often are used as a baseline for other models:</p><ul><li>naive is a simplistic forecasting approach where we predict all future values relying solely on the observation at the last available point of time.</li><li>snaive (seasonal naive) is a model that makes constant predictions taking into account information about seasonality. For instance, in the case of weekly seasonal data for each future Monday, we would predict the value from the last Monday, and for all future Tuesdays we would use the value from the last Tuesday and so on.</li><li>mean uses the averaged value of data as a forecast.</li><li>arima stands for <em>Autoregressive Integrated Moving Average</em>, see <a href="https://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average">Wikipedia</a> for details.</li><li>ets stands for <em>Exponential Smoothing</em>, see <a href="https://en.wikipedia.org/wiki/Exponential_smoothing">Wikipedia</a> for more.</li></ul><h3>3. Practice with Facebook Prophet</h3><h4>3.1 Installation in Python</h4><p>First, you need to install the library. Prophet is available for Python and R. The choice will depend on your personal preferences and project requirements. Further in this article we will use Python.</p><p>In Python you can install Prophet using PyPI:</p><pre>$ pip install fbprophet</pre><p>In R you can find the corresponing CRAN package. Refer to the <a href="https://facebookincubator.github.io/prophet/docs/installation.html">documentation</a> for details.</p><p>Let’s import the modules that we will need, and initialize our environment:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/59e924fdaefe57168e845489eea21eaf/href">https://medium.com/media/59e924fdaefe57168e845489eea21eaf/href</a></iframe><h4>3.2 Dataset</h4><p>We will predict the daily number of posts published on <a href="https://medium.com/">Medium</a>.</p><p>First, we load our <a href="https://drive.google.com/file/d/1G3YjM6mR32iPnQ6O3f6rE9BVbhiTiLyU/view">dataset</a>:</p><pre>df = pd.read_csv(&#39;../../data/medium_posts.csv&#39;, sep=&#39;\t&#39;)</pre><p>Next, we leave out all columns except published and url. The former corresonds to the time dimension while the latter uniquely identifies a post by its URL. Along the way we get rid of possible duplicates and missing values in the data:</p><pre>df = df[[&#39;published&#39;, &#39;url&#39;]].dropna().drop_duplicates()</pre><p>Next, we need to convert published to the datetime format because by default pandas treats this field as string-valued.</p><pre>df[&#39;published&#39;] = pd.to_datetime(df[&#39;published&#39;])</pre><p>Let’s sort the dataframe by time and take a look at what we’ve got:</p><pre>df.sort_values(by=[&#39;published&#39;]).head(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/1*mC2oY2Zbg9ZkiKHJoTMGxw.png" /></figure><p>Medium’s public release date was August 15, 2012. But, as you can see from the data above, there are at least several rows with much earlier publication dates. They have somehow turned up in our dataset, but they are hardly legitimate ones. We will just trim our time series to keep only those rows that fall onto the period from August 15, 2012 to June 25, 2017:</p><pre>df = df[(df[&#39;published&#39;] <strong>&gt;</strong> &#39;2012-08-15&#39;) <strong>&amp;</strong> <br>        (df[&#39;published&#39;] <strong>&lt;</strong> &#39;2017-06-26&#39;)].<br>         sort_values(by=[&#39;published&#39;])<br>df.head(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/493/1*ItNomHpyjyPUOoQpudabnw.png" /></figure><pre>df.tail(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/508/1*zdvREKo8BUMh4kFKVF3FZQ.png" /></figure><p>As we are going to predict the number of published posts, we will aggregate and count unique posts at each given point in time. We will name the corresponding new column posts:</p><pre>aggr_df = df.groupby(&#39;published&#39;)[[&#39;url&#39;]].count()<br>aggr_df.columns = [&#39;posts&#39;]</pre><p>In this practice, we are interested in the number of posts <strong>a day</strong>. But at this moment all our data is divided into irregular time intervals that are less than a day. This is called a <em>sub-daily time series</em>. To see it, let’s print out the first 3 rows:</p><pre>aggr_df.head(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/202/1*JfeFLcSDN3PHGx8mePi1qQ.png" /></figure><p>To fix this, we need to aggregate the post counts by “bins” of a date size. In time series analysis, this process is referred to as <em>resampling</em>. And if we <em>reduce</em> the sampling rate of data it is often called <em>downsampling</em>.</p><p>Luckily, pandas has a built-in functionality for this task. We will resample our time index down to 1-day bins:</p><pre>daily_df = aggr_df.resample(&#39;D&#39;).apply(sum)<br>daily_df.head(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/133/1*Hz3yos1yjhausJqXeKz2hA.png" /></figure><h4>3.3 Exploratory visual analysis</h4><p>As always, it may be helpful and instructive to look at a graphical representation of your data.</p><p>We will create a time series plot for the whole time range. Displaying data over such a long period of time can give clues about seasonality and conspicuous abnormal deviations.</p><p>First, we import and initialize the Plotly library, which allows creating beautiful interactive plots:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1b9a93027521bada443a5da9772b414a/href">https://medium.com/media/1b9a93027521bada443a5da9772b414a/href</a></iframe><p>We also define a helper function, which will plot our dataframes throughout the article:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/372da3e4a69154d0183602e23f574430/href">https://medium.com/media/372da3e4a69154d0183602e23f574430/href</a></iframe><p>Let’s try and plot our dataset <em>as is</em>:</p><pre>plotly_df(daily_df, title=&#39;Posts on Medium (daily)&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/876/1*0qA9CombWtV_piPtIcZeNA.png" /></figure><p>High-frequency data can be rather difficult to analyze. Even with the ability to zoom in provided by Plotly, it is hard to infer anything meaningful from this chart apart from the prominent upward and accelerating trend.</p><p>To reduce the noise, we will resample the post counts down to weekly bins. Besides <em>binning</em>, other possible techniques of noise reduction include <a href="https://en.wikipedia.org/wiki/Moving_average">Moving-Average Smoothing</a> and <a href="https://en.wikipedia.org/wiki/Exponential_smoothing">Exponential Smoothing</a>, among others.</p><p>We save our downsampled dataframe in a separate variable because further in this practice we will work only with daily series:</p><pre>weekly_df = daily_df.resample(&#39;W&#39;).apply(sum)</pre><p>Finally, we plot the result:</p><pre>plotly_df(weekly_df, title=&#39;Posts on Medium (weekly)&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/868/1*gILCukpYmBF5mbGpzc6HGg.png" /></figure><p>This downsampled chart proves to be somewhat better for an analyst’s perception.</p><p>One of the most useful functions that Plotly provides is the ability to quickly dive into different periods of timeline in order to better understand the data and find visual clues about possbile trends, periodic and irregular effects.</p><p>For example, zooming-in on a couple of consecutive years shows us time points corresponding to Christmas holidays, which greatly influence human behaviors.</p><p>Now, we’re going to omit the first few years of observations, up to 2015. First, they won’t contribute much into the forecast quality in 2017. Second, these first years, having very low number of posts per day, are likely to increase noise in our predictions, as the model would be forced to fit this abnormal historical data along with more relevant and indicative data from the recent years.</p><pre>daily_df = daily_df.loc[daily_df.index <strong>&gt;</strong>= &#39;2015-01-01&#39;]<br>daily_df.head(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/129/1*LSavHe80mka5mGdl6oBw-w.png" /></figure><p>To sum up, from visual analysis we can see that our dataset is non-stationary with a prominent growing trend. It also demonstrates weekly and yearly seasonality and a number of abnormal days in each year.</p><h4>3.4 Making a forecast</h4><p>Prophet’s API is very similar to the one you can find in sklearn. First we create a model, then call the method fit, and, finally, make a forecast. The input to the method fit is a DataFrame with two columns:</p><ul><li>ds (datestamp) must be of type date or datetime.</li><li>y is a numeric value we want to predict.</li></ul><p>To get started, we’ll import the library and mute unimportant diagnostic messages:</p><pre><strong>from</strong> fbprophet <strong>import</strong> Prophet<br><strong>import</strong> logging</pre><pre>logging.getLogger().setLevel(logging.ERROR)</pre><p>Let’s convert our dataframe to the format required by Prophet:</p><pre>df = daily_df.reset_index()<br>df.columns = [&#39;ds&#39;, &#39;y&#39;]<br>df.tail(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/156/1*YQ2Bb0R3fmzL_kJHAEPdGw.png" /></figure><p>The authors of the library generally advise to make predictions based on at least several months, ideally, more than a year of historical data. Luckily, in our case we have more than a couple of years of data to fit the model.</p><p>To measure the quality of our forecast, we need to split our dataset into the <em>historical part</em>, which is the first and biggest slice of our data, and the <em>prediction part</em>, which will be located at the end of the timeline. We will remove the last month from the dataset in order to use it later as a prediction target:</p><pre>prediction_size = 30<br>train_df = df[:<strong>-</strong>prediction_size]<br>train_df.tail(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/157/1*DrKFcH7Fv04kGSLE9yjdlQ.png" /></figure><p>Now we need to create a new Prophet object. Here we can pass the parameters of the model into the constructor. But in this article we will use the defaults. Then we train our model by invoking its fit method on our training dataset:</p><pre>m = Prophet()<br>m.fit(train_df);</pre><p>Using the helper method Prophet.make_future_dataframe, we create a dataframe which will contain all dates from the history and also extend into the future for those 30 days that we left out before.</p><pre>future = m.make_future_dataframe(periods=prediction_size)<br>future.tail(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/119/1*UkTdCrJOOw4TKr2uATxJdg.png" /></figure><p>We predict values with Prophet by passing in the dates for which we want to create a forecast. If we also supply the historical dates (as in our case), then in addition to the prediction we will get an in-sample fit for the history. Let&#39;s call the model&#39;s predict method with our future dataframe as an input:</p><pre>forecast = m.predict(future)<br>forecast.tail(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1018/1*vwkQmZiyRL-NgL50bOQ5_Q.png" /></figure><p>In the resulting dataframe you can see many columns characterizing the prediction, including trend and seasonality components as well as their confidence intervals. The forecast itself is stored in the yhat column.</p><p>The Prophet library has its own built-in tools for visualization that enable us to quickly evaluate the result.</p><p>First, there is a method called Prophet.plot that plots all the points from the forecast:</p><pre>m.plot(forecast);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/721/1*RVsHid0EwGhrmVMv6EUDuA.png" /></figure><p>This chart doesn’t look very informative. The only definitive conclusion that we can draw here is that the model treated many of the data points as outliers.</p><p>The second function Prophet.plot_components might be much more useful in our case. It allows us to observe different components of the model separately: trend, yearly and weekly seasonality. In addition, if you supply information about holidays and events to your model, they will also be shown in this plot.</p><p>Let’s try it out:</p><pre>m.plot_components(forecast);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/796/1*4UMsSOYztLZqZDVM7kiPQA.png" /></figure><p>As you can see from the trend graph, Prophet did a good job by fitting the accelerated growth of new posts at the end of 2016. The graph of weekly seasonality leads to the conclusion that usually there are less new posts on Saturdays and Sundays than on the other days of the week. In the yearly seasonality graph there is a prominent dip on Christmas Day.</p><h4>3.5 Forecast quality evaluation</h4><p>Let’s evaluate the quality of the algorithm by calculating the error metrics for the last 30 days that we predicted. For this, we will need the observations <em>yi</em> and the corresponding predicted values<em> ŷi</em>.</p><p>Let’s look into the object forecast that the library created for us:</p><pre>print(&#39;, &#39;.join(forecast.columns))</pre><pre><em>ds, trend, trend_lower, trend_upper, yhat_lower, yhat_upper, seasonal, seasonal_lower, seasonal_upper, seasonalities, seasonalities_lower, seasonalities_upper, weekly, weekly_lower, weekly_upper, yearly, yearly_lower, yearly_upper, yhat</em></pre><p>We can see that this dataframe contains all the information we need except for the historical values. We need to join the forecast object with the actual values y from the original dataset df. For this we will define a helper function that we will reuse later:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/56f87d5b6023b0f428c94fbd946f889c/href">https://medium.com/media/56f87d5b6023b0f428c94fbd946f889c/href</a></iframe><p>Let’s apply this function to our last forecast:</p><pre>cmp_df = make_comparison_dataframe(df, forecast)<br>cmp_df.tail(n=3)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/356/1*Y6ONuWnHsW0hPoQyMqfT6w.png" /></figure><p>We are also going to define a helper function that we will use to gauge the quality of our forecasting with MAPE and MAE error measures:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/97f4905b60335305192f180c1be3cacb/href">https://medium.com/media/97f4905b60335305192f180c1be3cacb/href</a></iframe><p>Let’s use our function:</p><pre><strong>for</strong> err_name, err_value <strong>in</strong> <br> calculate_forecast_errors(cmp_df, prediction_size).items():<br>    print(err_name, err_value)</pre><pre><em>MAPE 22.7243579814<br>MAE 70.452857085</em></pre><p>As a result, the relative error of our forecast (MAPE) is about 22.72%, and on average our model is wrong by 70.45 posts (MAE).</p><h4>3.6 Visualization</h4><p>Let’s create our own visualization of the model built by Prophet. It will comprise the actual values, forecast and confidence intervals.</p><p>First, we will plot the data for a shorter period of time to make the data points easier to distinguish. Second, we will show the model performance only for the period that we predicted, that is the last 30 days. It seems that these two measures should give us a more legible plot.</p><p>Third, we will use Plotly to make our chart interactive, which is great for exploring.</p><p>We will define a custom helper function show_forecast and call it (for more on how it works please refer to the comments in the code and the <a href="https://plot.ly/python/">documentation</a>):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1ec16d80a5d18d5f9a10c3486ece8b1f/href">https://medium.com/media/1ec16d80a5d18d5f9a10c3486ece8b1f/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/938/1*6BUeNE72hOHW4tq6WUIGuA.png" /></figure><p>At first glance, the prediction of the mean values by our model seems to be sensible. The high value of MAPE that we got above may be explained by the fact that the model failed to catch on to increasing peak-to-peak amplitude of weakly seasonality.</p><p>Also, we can conclude from the graph above that many of the actual values lie outside the confidence interval. Prophet may not be suitable for time series with unstable variance, at least when the default settings are used. We will try to fix this by applying a transform to our data.</p><h3>4. Box-Cox Transformation</h3><p>So far we have used Prophet with the default settings and the original data. We will leave the parameters of the model alone. But despite this we still have some room for improvement. In this section, we will apply the <a href="http://onlinestatbook.com/2/transformations/box-cox.html">Box–Cox transformation</a> to our original series. Let’s see where it will lead us.</p><p>A few words about this transformation. This is a monotonic data transformation that can be used to stabilize variance. We will use the one-parameter Box–Cox transformation, which is defined by the following expression:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/268/1*-Rq8QRDVgMtf8SrTUKNFmw.png" /></figure><p>We will need to implement the inverse of this function in order to be able to restore the original data scale. It is easy to see that the inverse is defined as:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/346/1*fJp908eI59q2llC3RbNyLw.png" /></figure><p>The corresponding function in Python is implemented as follows:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7269d315256b4756140b56369010e9ed/href">https://medium.com/media/7269d315256b4756140b56369010e9ed/href</a></iframe><p>First, we prepare our dataset by setting its index:</p><pre>train_df2 = train_df.copy().set_index(&#39;ds&#39;)</pre><p>Then, we apply the function stats.boxcox from Scipy, which applies the Box–Cox transformation. In our case it will return two values. The first one is the transformed series and the second one is the found value of <em>λ</em> that is optimal in terms of the maximum log-likelihood:</p><pre>train_df2[&#39;y&#39;], lambda_prophet = stats.boxcox(train_df2[&#39;y&#39;])<br>train_df2.reset_index(inplace=<strong>True</strong>)</pre><p>We create a new Prophet model and repeat the fit-predict cycle that we have already done above:</p><pre>m2 = Prophet()<br>m2.fit(train_df2)<br>future2 = m2.make_future_dataframe(periods=prediction_size)<br>forecast2 = m2.predict(future2)</pre><p>At this point, we need to revert the Box–Cox transformation with our inverse function and the known value of <em>λ</em>:</p><pre><strong>for</strong> column <strong>in</strong> [&#39;yhat&#39;, &#39;yhat_lower&#39;, &#39;yhat_upper&#39;]:<br>    forecast2[column] = inverse_boxcox(forecast2[column],<br>                                       lambda_prophet)</pre><p>Here we will reuse our tools for making the comparison dataframe and calculating the errors:</p><pre>cmp_df2 = make_comparison_dataframe(df, forecast2)<br><strong>for</strong> err_name, err_value <strong>in</strong> calculate_forecast_errors(cmp_df2, prediction_size).items():<br>     print(err_name, err_value)</pre><pre><em>MAPE 11.5921879552<br>MAE 39.072031256</em></pre><p>So, we can definitely state an increase in the quality of the model.</p><p>Finally, let’s plot our previous performance with the latest results side-by-side. Note that we use prediction_size for the third parameter in order to zoom in on the interval being predicted:</p><pre>show_forecast(cmp_df, prediction_size, 100, &#39;No transformations&#39;)<br>show_forecast(cmp_df2, prediction_size, 100, &#39;Box–Cox transformation&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/935/1*BBPRgRC8OUM8GOg0Z3oWEQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/928/1*rQlhl10iwKJw6ElgQH2X-Q.png" /></figure><p>We see that the forecast of weekly changes in the second graph is much closer to the real values now.</p><h3>5. Summary</h3><p>We have taken a look at <em>Prophet</em>, an open-source forecasting library that is specifically targeted at business time series. We have also done some hands-on practice in time series prediction.</p><p>As we have seen, the Prophet library does not make wonders, and its predictions out-of-box are not <a href="https://en.wikipedia.org/wiki/No_free_lunch_in_search_and_optimization">ideal</a>. It is still up to the data scientist to explore the forecast results, tune model parameters and transform data when necessary.</p><p>However, this library is user-friendly and easily customizable. The sole ability to take into account abnormal days that are known to the analyst beforehand might make a difference in some cases.</p><p>All in all, the Prophet library is worth being a part of your analytical toolbox.</p><h3>6. References</h3><ul><li>Official <a href="https://github.com/facebookincubator/prophet">Prophet repository</a> on GitHub.</li><li>Official <a href="https://facebookincubator.github.io/prophet/docs/quick_start.html">Prophet documentation</a>.</li><li>Sean J. Taylor, Benjamin Letham <a href="https://facebookincubator.github.io/prophet/static/prophet_paper_20170113.pdf">“Forecasting at scale”</a> — scientific paper explaining the algorithm which lays the foundation of Prophet.</li><li><a href="http://pbpython.com/prophet-overview.html">Forecasting Website Traffic Using Facebook’s Prophet Library</a> — Prophet overview with an example of website traffic forecasting.</li><li>Rob J. Hyndman, George Athanasopoulos <a href="https://www.otexts.org/fpp">“Forecasting: principles and practice”</a> — a very good online book about time series forecasting.</li></ul><p><em>Author: </em><a href="http://linkedin.com/in/egor-polusmak/"><em>Egor Polusmak</em></a><em>, Head of Data Analytics at Mail.Ru Search.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3f3af145cdc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-9-part-3-predicting-the-future-with-facebook-prophet-3f3af145cdc">Open Machine Learning Course. Topic 9. Part 2. Predicting the future with Facebook Prophet</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 9. Part 1. Time series analysis in Python]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-9-time-series-analysis-in-python-a270cb05e0b3?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/a270cb05e0b3</guid>
            <category><![CDATA[education]]></category>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Dima Sergeev]]></dc:creator>
            <pubDate>Tue, 10 Apr 2018 09:41:17 GMT</pubDate>
            <atom:updated>2024-11-25T19:35:31.061Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*hJ_P0bVRLNNjcc5st5lu-A.jpeg" /></figure><p>Hi there!</p><p>We continue our open machine learning course with a new article on time series.</p><p>Let’s take a look at how to work with time series in Python, what methods and models we can use for prediction; what’s double and triple exponential smoothing; what to do if stationarity is not you favorite game; how to build SARIMA and stay alive; how to make predictions using xgboost. All of this will be applied to (harsh) real world example.</p><p>For more data science articles consider following my blog: <a href="https://datawondering.com/">DataWondering.com</a></p><h3>Article outline</h3><ol><li>Introduction<br> — Basic definitions<br> — Quality metrics</li><li>Move, smoothe, evaluate<br> — Rolling window estimations<br> — Exponential smoothing, Holt-Winters model<br> — Time-series cross validation, parameters selection</li><li>Econometric approach<br> — Stationarity, unit root<br> — Getting rid of non-stationarity<br> — SARIMA intuition and model building</li><li>Linear (and not quite) models on time series<br> — Feature extraction<br> — Linear models, feature importance<br> — Regularization, feature selection<br> — XGBoost</li></ol><p><em>The following content is better viewed and reproduced as a </em><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic09_time_series/topic9_part1_time_series_python.ipynb"><em>Jupyter-notebook</em></a></p><p>In my day to day job I encounter time series-connected tasks almost every day. The most frequent question is — what will happen with our metrics in the next day/week/month/etc. — how many players will install the app, how much time will they spend online, how many actions users will do, and so forth. We can approach prediction task using different methods, depending on the required quality of the prediction, length of the forecasted period, and, of course, time we have to choose features and tune parameters to achieve desired results.</p><h3>Introduction</h3><blockquote>Small <a href="https://en.wikipedia.org/wiki/Time_series">definition</a> of time series:<br><em>Time series — is a series of data points indexed (or listed or graphed) in time order.</em></blockquote><p>Therefore data is organized around relatively deterministic timestamps, and therefore, compared to random samples, may contain additional information that we will try to extract.</p><p>Let’s import some libraries. First and foremost we will need <a href="http://statsmodels.sourceforge.net/stable/">statsmodels</a> library that has tons of statistical modeling functions, including time series. For R afficionados (that had to move to python) statsmodels will definitely look familiar as it supports model definitions like ‘Wage ~ Age + Education’.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/832e80066c25fd621be3da1355f2a812/href">https://medium.com/media/832e80066c25fd621be3da1355f2a812/href</a></iframe><p>As an example let’s use some real mobile game data on hourly ads watched by players and daily in-game currency spent:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7b06888ad3d459e2be927eba1ca1f633/href">https://medium.com/media/7b06888ad3d459e2be927eba1ca1f633/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*Xglh0CGdddyPT2PdXbfSUA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/897/1*l43t8fEGWuxx8KmDC0eHNw.png" /></figure><h3>Forecast quality metrics</h3><p>Before actually forecasting, let’s understand how to measure the quality of predictions and have a look at the most common and widely used metrics</p><ul><li><a href="http://scikit-learn.org/stable/modules/model_evaluation.html#r2-score-the-coefficient-of-determination"><strong>R squared</strong></a>, coefficient of determination (in econometrics it can be interpreted as a percentage of variance explained by the model), (<em>-inf</em>, 1] sklearn.metrics.r2_score</li><li><a href="http://scikit-learn.org/stable/modules/model_evaluation.html#mean-absolute-error"><strong>Mean Absolute Error</strong></a>, it is an interpretable metric because it has the same unit of measurement as the initial series, [0, +<em>inf</em>)<br>sklearn.metrics.mean_absolute_error</li><li><a href="http://scikit-learn.org/stable/modules/model_evaluation.html#median-absolute-error"><strong>Median Absolute Error</strong></a>, again an interpretable metric, particularly interesting because it is robust to outliers, [0, +<em>inf</em>)<br>sklearn.metrics.median_absolute_error</li><li><a href="http://scikit-learn.org/stable/modules/model_evaluation.html#mean-squared-error"><strong>Mean Squared Error</strong></a>, most commonly used, gives higher penalty to big mistakes and vise versa, [0, +<em>inf)<br></em>sklearn.metrics.mean_squared_error</li><li><a href="http://scikit-learn.org/stable/modules/model_evaluation.html#mean-squared-logarithmic-error"><strong>Mean Squared Logarithmic Error</strong></a>, practically the same as MSE but we initially take logarithm of the series, as a result we give attention to small mistakes as well, usually is used when data has exponential trends, [0, +<em>inf</em>)<br>sklearn.metrics.mean_squared_log_error</li><li><strong>Mean Absolute Percentage Error</strong>, same as MAE but percentage, — very convenient when you want to explain the quality of the model to your management, [0, +<em>inf</em>), not implemented in sklearn</li></ul><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b3aedf7b05c4aeb50d7e663e3a842485/href">https://medium.com/media/b3aedf7b05c4aeb50d7e663e3a842485/href</a></iframe><p>Excellent, now we know how to measure the quality of the forecasts, what metrics can we use and how to translate the results to the boss. Little thing is left — building the model.</p><h3>Move, smoothe, evaluate</h3><p>Let’s start with a naive hypothesis — “tomorrow will be the same as today”, but instead of a model like ŷ(t)=y(t−1) (which is actually a great baseline for any time series prediction problems and sometimes it’s impossible to beat it with any model) we’ll assume that the future value of the variable depends on the average <strong><em>n</em></strong> of its previous values and therefore we’ll use <strong>moving average</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xKNEAvd2OU0T71Y7rZu0ZA.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d1131e654b4953aee48a7b12481ddc5/href">https://medium.com/media/1d1131e654b4953aee48a7b12481ddc5/href</a></iframe><pre>Out: 116805.0</pre><p>Unfortunately we can’t make this prediction long-term — to get one for the next step we need the previous value to be actually observed. But moving average has another use case — smoothing of the original time series to indicate trends. Pandas has an implementation available <a href="http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.rolling.html">DataFrame.rolling(window).mean()</a>. The wider the window - the smoother will be the trend. In the case of the very noisy data, which can be very often encountered in finance, this procedure can help to detect common patterns.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cd63ae72e4f4300fdfd4b96048d53ceb/href">https://medium.com/media/cd63ae72e4f4300fdfd4b96048d53ceb/href</a></iframe><p>Smoothing by last 4 hours<br>plotMovingAverage(ads, 4)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*g-79VWtvLlJA5rcISd5pPw.png" /></figure><p>Smoothing by last 12 hours<br>plotMovingAverage(ads, 12)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*T71bcR_9UBe8MbevTgYTEQ.png" /></figure><p>Smoothing by 24 hours — we get daily trend<br>plotMovingAverage(ads, 24)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*Xfxf0YM_VDgUhpw_USfJGw.png" /></figure><p>As you can see, applying daily smoothing on hour data allowed us to clearly see the dynamics of ads watched. During the weekends the values are higher (weekends — time to play) and weekdays are generally lower.</p><p>We can also plot confidence intervals for our smoothed values<br>plotMovingAverage(ads, 4, plot_intervals=True)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*FzVb4xiHV6J1aTTAM7OdQw.png" /></figure><p>And now let’s create a simple anomaly detection system with the help of the moving average. Unfortunately, in this particular series everything is more or less normal, so we’ll intentionally make one of the values abnormal in the dataframe ads_anomaly</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/47999b06d6e61d685c9795918504de7c/href">https://medium.com/media/47999b06d6e61d685c9795918504de7c/href</a></iframe><p>Let’s see, if this simple method can catch the anomaly<br>plotMovingAverage(ads_anomaly, 4, plot_intervals=True, plot_anomalies=True)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*RXW9AGTcitJgMS7L-YJ3ZQ.png" /></figure><p>Neat! What about the second series (with weekly smoothing)?<br>plotMovingAverage(currency, 7, plot_intervals=True, plot_anomalies=True)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/897/1*ml1RV_36gA8hx_lFhRIqHA.png" /></figure><p>Oh no! Here is the downside of our simple approach — it did not catch monthly seasonality in our data and marked almost all 30-day peaks as an anomaly. If you don’t want to have that many false alarms — it’s best to consider more complex models.</p><p><strong>Weighted average</strong> is a simple modification of the moving average, inside of which observations have different weights summing up to one, usually more recent observations have greater weight.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8IBEmsFCywApUE_joR0aqA.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4d8f936443461fb4ea74731cddba711f/href">https://medium.com/media/4d8f936443461fb4ea74731cddba711f/href</a></iframe><pre>Out: 98423.0</pre><h3>Exponential smoothing</h3><p>And now let’s take a look at what happens if instead of weighting the last nn values of the time series we start weighting all available observations while exponentially decreasing weights as we move further back in historical data. There’s a formula of the simple <a href="https://en.wikipedia.org/wiki/Exponential_smoothing"><strong>exponential smoothing</strong></a> that will help us in that:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JJZyOq_GySQnx8N1ZZ-UfQ.png" /></figure><p>Here the model value is a weighted average between the current true value and the previous model values. The α weight is called a smoothing factor. It defines how quickly we will “forget” the last available true observation. The less α is the more influence previous model values have, and the smoother the series is.</p><p>Exponentiality is hiding in the recursivity of the function — we multiply each time (1−α) by the previous model value which, in its turn, also containes (1−α) and so forth until the very beginning.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0bf8f9e6ba75e585f317f5e2fe572473/href">https://medium.com/media/0bf8f9e6ba75e585f317f5e2fe572473/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*iP_blo7a9pCJ9EB4ZcOYcw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/897/1*L_w3qDoexk4ba3LKMXcqwQ.png" /></figure><h3>Double exponential smoothing</h3><p>Until now all we could get from our methods in the best case was just a single future point prediction (and also some nice smoothing), that’s cool but not enough, so let’s extend exponential smoothing so that we can predict two future points (of course, we also get some smoothing).</p><p>Series decomposition should help us — we obtain two components: intercept (also, level) ℓ and trend (also, slope) b. We’ve learnt to predict intercept (or expected series value) using previous methods, and now we will apply the same exponential smoothing to the trend, believing naively or perhaps not that the future direction of the time series changes depends on the previous weighted changes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ws6cwxbpczBgTGYEZep79Q.png" /></figure><p>As a result we get a set of functions. The first one describes intercept, as before it depends on the current value of the series, and the second term is now split into previous values of the level and of the trend. The second function describes trend — it depends on the level changes at the current step and on the previous value of the trend. In this case β coefficient is a weight in the exponential smoothing. The final prediction is the sum of the model values of the intercept and trend.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/283ca910d732eb0a16254b23af4046e5/href">https://medium.com/media/283ca910d732eb0a16254b23af4046e5/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c9XHSuc9d83bmopHJVnPLA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LrBONoISc3sb1HvwJNoN9A.png" /></figure><p>Now we have to tune two parameters — α and β. The former is responsible for the series smoothing around trend, and the latter for the smoothing of the trend itself. The bigger the values, the more weight the latest observations will have and the less smoothed the model series will be. Combinations of the parameters may produce really weird results, especially if set manually. We’ll look into choosing parameters automatically in a bit, immediately after triple exponential smoothing.</p><h3>Triple exponential smoothing a.k.a. Holt-Winters</h3><p>Hooray! We’ve successfully reached our next variant of exponential smoothing, this time triple.</p><p>The idea of this method is that we add another, third component — seasonality. This means we should’t use the method if our time series do not have seasonality, which is not the case in our example. Seasonal component in the model will explain repeated variations around intercept and trend, and it will be described by the length of the season, in other words by the period after which variations repeat. For each observation in the season there’s a separate component, for example, if the length of the season is 7 (weekly seasonality), we will have 7 seasonal components, one for each day of the week.</p><p>Now we get a new system:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tQUjJKDKmqjGPeQ9YqV_dQ.png" /></figure><p>Intercept now depends on the current value of the series minus corresponding seasonal component, trend stays unchanged, and the seasonal component depends on the current value of the series minus intercept and on the previous value of the component. Please take into account that the component is smoothed through all the available seasons, for example, if we have a Monday component then it will only be averaged with other Mondays. You can read more on how averaging works and how initial approximation of the trend and seasonal components is done <a href="http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm">here</a>. Now that we have seasonal component we can predict not one and not even two but arbitrary mm future steps which is very encouraging.</p><p>Below is the code for a triple exponential smoothing model, also known by the last names of its creators — Charles Holt and his student Peter Winters. Additionally Brutlag method was included into the model to build confidence intervals:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6Cu5nv-COdclmdfXnAU02Q.png" /></figure><p>where T is the length of the season, d is the predicted deviation, and the other parameters were taken from the triple exponential smoothing. You can read more about the method and its applicability to anomalies detection in time series <a href="http://fedcsis.org/proceedings/2012/pliks/118.pdf">here</a>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/13d018f43b2a8fce21157f0174ff5010/href">https://medium.com/media/13d018f43b2a8fce21157f0174ff5010/href</a></iframe><h3>Time series cross validation</h3><p>Before we start building model let’s talk first about how to estimate model parameters automatically.</p><p>There’s nothing unusual here, as always we have to choose a loss function suitable for the task, that will tell us how close the model approximates data. Then using cross-validation we will evaluate our chosen loss function for given model parameters, calculate gradient, adjust model parameters and so forth, bravely descending to the global minimum of error.</p><p>The question is how to do cross-validation on time series, because, you know, time series do have time structure and one just can’t randomly mix values in a fold without preserving this structure, otherwise all time dependencies between observations will be lost. That’s why we will have to use a bit more tricky approach to optimization of the model parameters, I don’t know if there’s an official name to it but on <a href="https://stats.stackexchange.com/questions/14099/using-k-fold-cross-validation-for-time-series-model-selection">CrossValidated</a>, where one can find all the answers but the Answer to the Ultimate Question of Life, the Universe, and Everything, “cross-validation on a rolling basis” was proposed as a name.</p><p>The idea is rather simple — we train our model on a small segment of the time series, from the beginning until some <strong><em>t</em></strong>, make predictions for the next <strong><em>t+n </em></strong>steps and calculate an error. Then we expand our training sample until <strong><em>t+n</em></strong> value and make predictions from <strong><em>t+n</em></strong> until <strong><em>t+2∗n</em></strong>, and we continue moving our test segment of the time series until we hit the last available observation. As a result we have as many folds as many <strong><em>n</em></strong> will fit between the initial training sample and the last observation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/631/1*6ujHlGolRTGvspeUDRe1EA.png" /></figure><p>Now, knowing how to set cross-validation, we will find optimal parameters for the Holt-Winters model, recall that we have daily seasonality in ads, hence the slen=24 parameter</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f2d86e3b66198039efec1f7405ff0815/href">https://medium.com/media/f2d86e3b66198039efec1f7405ff0815/href</a></iframe><p>In the Holt-Winters model, as well as in the other models of exponential smoothing, there’s a constraint on how big smoothing parameters could be, each of them is in the range from 0 to 1, therefore to minimize loss function we have to choose an algorithm that supports constraints on model parameters, in our case — Truncated Newton conjugate gradient.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/73be269f00a8c1a1c5d061fb341d29fb/href">https://medium.com/media/73be269f00a8c1a1c5d061fb341d29fb/href</a></iframe><pre>0.11652680227350454 0.002677697431105852 0.05820973606789237<br>CPU times: user 1.96 s, sys: 17.3 ms, total: 1.98 s<br>Wall time: 2 s</pre><p>Chart rendering code</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3aeee8993d1fefafbcb9cab658285b11/href">https://medium.com/media/3aeee8993d1fefafbcb9cab658285b11/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5GDW3uGcxkgoUfAxWMN7eQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DSXPcnHnvnZRH8YijMOfMg.png" /></figure><p>Judging by the chart, our model was able to successfully approximate the initial time series, catching daily seasonality, overall downwards trend and even some anomalies. If you take a look at the modeled deviation, you can clearly see that the model reacts quite sharply to the changes in the structure of the series but then quickly returns deviation to the normal values, “forgetting” the past. This feature of the model allows us to quickly build anomaly detection systems even for quite noisy series without spending too much time and money on preparing data and training the model.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/39596efc13e7b45164e0ab70c5137afa/href">https://medium.com/media/39596efc13e7b45164e0ab70c5137afa/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i8KR9tLBxRQ5nUFtHjUvgA.png" /><figcaption>Deviation increases when we have anomalies</figcaption></figure><p>We’ll apply the same algorithm for the second series which, as we know, has trend and 30-day seasonality</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0c7394864a56f97e5b719faef02bf32f/href">https://medium.com/media/0c7394864a56f97e5b719faef02bf32f/href</a></iframe><pre>0.012841445048055122 0.04883371471892228 0.00943678056045777<br>CPU times: user 3.03 s, sys: 24.8 ms, total: 3.05 s<br>Wall time: 3.11 s</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Km4pL2wIDQGRE3-12rLFCg.png" /></figure><p>Looks quite adequate, model has caught both upwards trend and seasonal spikes and overall fits our values nicely</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b1D6G3IR4XUcP8oUvu6B0A.png" /><figcaption>Caught some anomalies as well</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G_Do4GDD2nAHjiXFyW-qaA.png" /><figcaption>Deviations increases as forecast period moves further</figcaption></figure><h3>Econometric approach</h3><h3>Stationarity</h3><p>Before we start modeling we should mention such an important property of time series as <a href="https://en.wikipedia.org/wiki/Stationary_process"><strong>stationarity</strong></a>.</p><p>If the process is stationary that means it doesn’t change its statistical properties over time, namely mean and variance do not change over time (constancy of variance is also called <a href="https://en.wikipedia.org/wiki/Homoscedasticity">homoscedasticity</a>), also covariance function does not depend on the time (should only depend on the distance between observations). You can see this visually on the pictures from the post of <a href="http://www.seanabu.com/2016/03/22/time-series-seasonal-ARIMA-model-in-python/">Sean Abu</a>:</p><ul><li>The red graph below is not stationary because the mean increases over time.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/586/0*qrYiVksz8g3drl5Z.png" /></figure><ul><li>We were unlucky with the variance, see the varying spread of values over time</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/577/0*fEqQDq_TaEqa511n.png" /></figure><ul><li>Finally, the covariance of the i th term and the (i + m) th term should not be a function of time. In the following graph, you will notice the spread becomes closer as the time increases. Hence, the covariance is not constant with time for the right chart.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/566/1*qJs3g2f77flIXr6mFsbPmw.png" /></figure><p>So why stationarity is so important? Because it’s easy to make predictions on the stationary series as we assume that the future statistical properties will not be different from the currently observed. Most of the time series models in one way or the other model and predict those properties (mean or variance, for example), that’s why predictions would be wrong if the original series were not stationary. Unfortunately most of the time series we see outside of textbooks are non-stationary but we can (and should) change this.</p><p>So, to fight non-stationarity we have to know our enemy so to say. Let’s see how to detect it. To do that we will now take a look at the white noise and random walks and we will learn how to get from one to another for free, without registration and SMS.</p><p>White noise chart:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/78f2deb127f15fa807f3542f30978a28/href">https://medium.com/media/78f2deb127f15fa807f3542f30978a28/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/869/1*C95wWTnA2hE2H9sBmFzQDw.png" /></figure><p>So the process generated by standard normal distribution is stationary and oscillates around 0 with with deviation of 1. Now based on this process we will generate a new one where each next value will depend on the previous one: x(t)=ρ*x(t−1)+e(t)</p><p>Chart rendering code</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f89cd6073bacdd0d50afdf4997465303/href">https://medium.com/media/f89cd6073bacdd0d50afdf4997465303/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/590/1*7jeb47aq-0WmNndzO_jxHQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/590/1*XLwz-8vtx51T9RvfhZ2e_A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/599/1*gMyyzlnnJHN1Nd1dZ8SL5g.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/596/1*ZhIS_d1-htkJtZM3QrYAGw.png" /></figure><p>On the first chart you can see the same stationary white noise you’ve seen before. On the second one the value of ρρ increased to 0.6, as a result wider cycles appeared on the chart but overall it is still stationary. The third chart deviates even more from the 0 mean but still oscillates around it. Finally, the value of ρ equal to 1 gives us a random walk process — non-stationary time series.</p><p>This happens because after reaching the critical value the series x(t)=ρ*x(t−1)+e(t) does not return to its mean value. If we subtract x(t−1) from the left and the right side we will get x(t)−x(t−1)=(ρ−1)*x(t−1)+e(t), where the expression on the left is called the first difference. If ρ=1 then the first difference gives us stationary white noise e(t). This fact is the main idea of the <a href="https://en.wikipedia.org/wiki/Dickey%E2%80%93Fuller_test">Dickey-Fuller test</a> for the stationarity of time series (presence of a unit root). If we can get stationary series from non-stationary using the first difference we call those series integrated of order 1. Null hypothesis of the test — time series is non-stationary, was rejected on the first three charts and was accepted on the last one. We’ve got to say that the first difference is not always enough to get stationary series as the process might be integrated of order d, d &gt; 1 (and have multiple unit roots), in such cases the augmented Dickey-Fuller test is used that checks multiple lags at once.</p><p>We can fight non-stationarity using different approaches — various order differences, trend and seasonality removal, smoothing, also using transformations like Box-Cox or logarithmic.</p><h3>Getting rid of non-stationarity and building SARIMA</h3><p>Now let’s build an ARIMA model by walking through all the circles of hell stages of making series stationary.</p><p>Chart rendering code</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8874120edb17ad6a42f18fe27ee13344/href">https://medium.com/media/8874120edb17ad6a42f18fe27ee13344/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/856/1*GSf3AiF0mNc9AScQsI1UTQ.png" /></figure><p>Surprisingly, initial series are stationary, Dickey-Fuller test rejected null hypothesis that a unit root is present. Actually, it can be seen on the plot itself — we don’t have a visible trend, so mean is constant, variance is pretty much stable throughout the series. The only thing left is seasonality which we have to deal with before modelling. To do so let’s take “seasonal difference” which means a simple subtraction of series from itself with a lag that equals the seasonal period.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e5786e114a0e7c21b697de1ff14d5ce0/href">https://medium.com/media/e5786e114a0e7c21b697de1ff14d5ce0/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/856/1*qD4gwPbKj3hFAah3F84r2g.png" /></figure><p>That’s better, visible seasonality is gone, however autocorrelation function still has too many significant lags. To remove them we’ll take first differences — subtraction of series from itself with lag 1</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/052e26c3634342ea56b15f8502d3b03a/href">https://medium.com/media/052e26c3634342ea56b15f8502d3b03a/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/856/1*t_5QyZgc8nuuVdRBKbV4iQ.png" /></figure><p>Perfect! Our series now look like something undescribable, oscillating around zero, Dickey-Fuller indicates that it’s stationary and the number of significant peaks in ACF has dropped. We can finally start modelling!</p><h3>ARIMA-family Crash-Course</h3><p>A few words about the model. Letter by letter we’ll build the full name — <strong>SARIMA(<em>p,d,q</em>)(<em>P,D,Q,s</em>)</strong>, Seasonal Autoregression Moving Average model:</p><ul><li><strong>AR(<em>p</em>) </strong>— autoregression model, i.e., regression of the time series onto itself. Basic assumption — current series values depend on its previous values with some lag (or several lags). The maximum lag in the model is referred to as <strong><em>p</em></strong>. To determine the initial <strong><em>p</em></strong> you need to have a look at PACF plot — find the biggest significant lag, after which <strong>most</strong> other lags are becoming not significant.</li><li><strong>MA(<em>q</em>)</strong> — moving average model. Without going into detail it models the error of the time series, again the assumption is — current error depends on the previous with some lag, which is referred to as <strong><em>q</em></strong>. Initial value can be found on ACF plot with the same logic.</li></ul><p>Let’s have a small break and combine the first 4 letters:</p><p><strong>AR(<em>p</em>) + MA(<em>q</em>) = ARMA(<em>p,q</em>)</strong></p><p>What we have here is the Autoregressive–moving-average model! If the series is stationary, it can be approximated with those 4 letters. Shall we continue?</p><ul><li><strong>I(<em>d</em>)</strong>— order of integration. It is simply the number of nonseasonal differences needed for making the series stationary. In our case it’s just 1, because we used first differences.</li></ul><p>Adding this letter to four previous gives us <strong>ARIMA</strong> model which knows how to handle non-stationary data with the help of nonseasonal differences. Awesome, last letter left!</p><ul><li><strong>S(<em>s</em>)</strong> — this letter is responsible for seasonality and equals the season period length of the series</li></ul><p>After attaching the last letter we find out that instead of one additional parameter we get three in a row — <strong><em>(P,D,Q)</em></strong></p><ul><li><strong><em>P</em></strong> — order of autoregression for seasonal component of the model, again can be derived from PACF, but this time you need to look at the number of significant lags, which are the multiples of the season period length, for example, if the period equals 24 and looking at PACF we see 24-th and 48-th lags are significant, that means initial <strong><em>P</em></strong> should be 2.</li><li><strong><em>Q </em></strong>— same logic, but for the moving average model of the seasonal component, use ACF plot</li><li><strong><em>D</em></strong> — order of seasonal integration. Can be equal to 1 or 0, depending on whether seasonal differences were applied or not</li></ul><p>Now, knowing how to set initial parameters, let’s have a look at the final plot once again and set the parameters:<br>tsplot(ads_diff[24+1:], lags=60)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/856/1*t_5QyZgc8nuuVdRBKbV4iQ.png" /></figure><ul><li><strong><em>p</em></strong> is most probably 4, since it’s the last significant lag on PACF after which most others are becoming not significant.</li><li><strong><em>d</em></strong> just equals 1, because we had first differences</li><li><strong><em>q</em></strong> should be somewhere around 4 as well as seen on ACF</li><li><strong><em>P</em></strong> might be 2, since 24-th and 48-th lags are somewhat significant on PACF</li><li><strong><em>D</em></strong> again equals 1 — we performed seasonal differentiation</li><li><strong><em>Q</em></strong> is probably 1, 24-th lag on ACF is significant, while 48-th is not</li></ul><p>Now we want to test various models and see which one is better</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c3b9bad201fbbc5cc19f18791f460397/href">https://medium.com/media/c3b9bad201fbbc5cc19f18791f460397/href</a></iframe><pre>Out: 54</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6c57bb142b13b6c970a6511e42942e6e/href">https://medium.com/media/6c57bb142b13b6c970a6511e42942e6e/href</a></iframe><pre>CPU times: user 41.3 s, sys: 2.76 s, total: 44.1 s<br>Wall time: 37.4 s</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/752b7f413125dd17fe0f226784d1bb8f/href">https://medium.com/media/752b7f413125dd17fe0f226784d1bb8f/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ETjGK2zslGmrH3HHvvDUhQ.png" /></figure><p>Let’s inspect the residuals of the model<br>tsplot(best_model.resid[24+1:], lags=60)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/856/1*q_bSXlMdmiwMA73fXOOK5A.png" /></figure><p>Well, it’s clear that the residuals are stationary, there are no apparent autocorrelations, let’s make predictions using our model</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d39a229ac4d44a7c405ebc6292ab3e0f/href">https://medium.com/media/d39a229ac4d44a7c405ebc6292ab3e0f/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/892/1*ccFZwDXwb2ZW1c8FuxRHLw.png" /></figure><p>In the end we got quite adequate predictions, our model on average was wrong by 4.01%, which is very very good, but overall costs of preparing data, making series stationary and brute-force parameters selecting might not be worth this accuracy.</p><h3>Linear (and not quite) models on time series</h3><p>Small lyrical digression again. Often in my job I have to build models with the only principle guiding me known as <a href="http://fastgood.cheap/"><em>fast, good, cheap</em></a>. That means some of the models will never be “production ready” as they demand too much time for the data preparation (for example, SARIMA), or require frequent re-training on new data (again, SARIMA), or are difficult to tune (good example — SARIMA), so it’s very often much easier to select a couple of features from the existing time series and build a simple linear regression or, say, a random forest. Good and cheap.</p><p>Maybe this approach is not backed up by theory, breaks different assumptions (like, Gauss-Markov theorem, especially about the errors being uncorrelated), but it’s very useful in practice and quite frequently used in machine learning competitions.</p><h3>Feature exctraction</h3><p>Alright, model needs features and all we have is a 1-dimentional time series to work with. What features can we exctract?</p><p><strong>Lags of time series, of course</strong></p><p><strong>Window statistics:</strong></p><ul><li>Max/min value of series in a window</li><li>Average/median value in a window</li><li>Window variance</li><li>etc.</li></ul><p><strong>Date and time features:</strong></p><ul><li>Minute of an hour, hour of a day, day of the week, you get it</li><li>Is this day a holiday? Maybe something special happened? Make it a boolean feature</li></ul><p><strong>Target encoding</strong></p><p><strong>Forecasts from other models</strong> (though we can lose the speed of prediction this way)</p><p>Let’s run through some of the methods and see what we can extract from our ads series</p><h3>Lags of time series</h3><p>Shifting the series <strong><em>n</em></strong> steps back we get a feature column where the current value of time series is aligned with its value at the time <strong><em>t−n</em></strong>. If we make a 1 lag shift and train a model on that feature, the model will be able to forecast 1 step ahead having observed current state of the series. Increasing the lag, say, up to 6 will allow the model to make predictions 6 steps ahead, however it will use data, observed 6 steps back. If something fundamentally changes the series during that unobserved period, the model will not catch the changes and will return forecasts with big error. So, during the initial lag selection one has to find a balance between the optimal prediction quality and the length of forecasting horizon.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7117097383239f7e517dd0806a913d32/href">https://medium.com/media/7117097383239f7e517dd0806a913d32/href</a></iframe><p>Wonderful, we got ourselves a dataset here, why don’t we train a model?</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a51ed6ea651623f995c69afff722f5c1/href">https://medium.com/media/a51ed6ea651623f995c69afff722f5c1/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0hLmlcFVnKljGCe-jmZDPg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/878/1*9K0FULC9peB6_zRgRnQUyA.png" /></figure><p>Well, simple lags and linear regression gave us predictions that are not that far from SARIMA in quality. There are lot’s of unnecessary features, but we’ll do feature selection a bit later. Now let’s continue engineering!</p><p>We’ll add into our dataset hour, day of the week and boolean for the weekend. To do so we need to transform current dataframe index into datetime format and exctract hour and weekday out of it.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/45a96df277f1621a3ac2ad077aa3aacc/href">https://medium.com/media/45a96df277f1621a3ac2ad077aa3aacc/href</a></iframe><p>We can visualize the resulting features</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/aa7d0fd9bcb3119f43c118ad9116e16f/href">https://medium.com/media/aa7d0fd9bcb3119f43c118ad9116e16f/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/925/1*-ZG-BbBqy_Xl1AjxW8jyYg.png" /><figcaption>Blue spiky line — hour feature, green ladder — weekday, red bump — weekends!</figcaption></figure><p>Since now we have different scales of variables — thousands for lag features and tens for categorical, it’s reasonable to transform them into same scale to continue exploring feature importances and later — regularization.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d54787795c7b7cbbd90514b1a5d5bea7/href">https://medium.com/media/d54787795c7b7cbbd90514b1a5d5bea7/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0vAkMWquV4dTgewDg94JYQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/886/1*NENact_8y6O1352EYzoG0A.png" /></figure><p>Test error goes down a little bit and judging by the coefficients plot we can say that weekday and is_weekend are rather useful features</p><h3>Target encoding</h3><p>I’d like to add another variant of encoding categorical variables — by mean value. If it’s undesirable to explode dataset by using tons of dummy variables that can lead to the loss of information about the distance, and if they can’t be used as real values because of the conflicts like “0 hours &lt; 23 hours”, then it’s possible to encode a variable with a little bit more interpretable values. Natural idea is to encode with the mean value of the target variable. In our example every day of the week and every hour of the day can be encoded by the corresponding average number of ads watched during that day or hour. It’s very important to make sure that the mean value is calculated over train set only (or over current cross-validation fold only), so that the model is not aware of the future.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a86724f8a6c0c4b6d6279d65eb02c8ac/href">https://medium.com/media/a86724f8a6c0c4b6d6279d65eb02c8ac/href</a></iframe><p>Let’s have a look at hour averages</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/09c0d915cbdc3bacd13e32f6dbd1d513/href">https://medium.com/media/09c0d915cbdc3bacd13e32f6dbd1d513/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/445/1*vnc2RhvCPIuPOVqTKKIjog.png" /></figure><p>Finally, put all the transformations together in a single function</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dba682908c70394da0d0c6905b95e1f8/href">https://medium.com/media/dba682908c70394da0d0c6905b95e1f8/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ujaq7SFvHBxPDeF8XFQOmA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/886/1*S0Pr4flzWrwhveJE9AWoeA.png" /></figure><p>Here comes <strong>overfitting</strong>! Hour_average variable was so great on train dataset that the model decided to concentrate all its forces on it - as a result the quality of prediction dropped. This problem can be approached in a variety of ways, for example, we can calculate target encoding not for the whole train set, but for some window instead, that way encodings from the last observed window will probably describe current series state better. Or we can just drop it manually, since we&#39;re sure here it makes things only worse.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f0b69c0f36adf4198a825e6f386b1312/href">https://medium.com/media/f0b69c0f36adf4198a825e6f386b1312/href</a></iframe><h3>Regularization and feature selection</h3><p>As we already know, not all features are equally healthy, some may lead to overfitting and should be removed. Besides manual inspecting we can apply regularization. Two most popular regression models with regularization are Ridge and Lasso regressions. They both add some more constrains to our loss function.</p><p>In case of <strong><em>Ridge regression</em></strong> — those constrains are the sum of squares of coefficients, multiplied by the regularization coefficient. I.e. the bigger coefficient feature has — the bigger our loss will be, hence we will try to optimize the model while keeping coefficients fairly low.</p><p>As a result of such regularization which has a proud name <strong><em>L2</em></strong> we’ll have higher bias and lower variance, so the model will generalize better (at least that’s what we hope will happen).</p><p>Second model —<strong><em> Lasso regression</em></strong>, here we add to the loss function not squares but absolute values of the coefficients, as a result during the optimization process coefficients of unimportant features may become zeroes, so Lasso regression allows for automated feature selection. This regularization type is called <strong><em>L1</em></strong>.</p><p>First, make sure we have things to drop and data truly has highly correlated features</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9ab2657b0a203150da1a57830b555aa6/href">https://medium.com/media/9ab2657b0a203150da1a57830b555aa6/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/597/1*KTxkDrHFdl7E6xs3Hq5_YA.png" /><figcaption>Prettier than some modern art</figcaption></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8bc464e4cd272e2949292c1fd87e1098/href">https://medium.com/media/8bc464e4cd272e2949292c1fd87e1098/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mh_iu9oxsBSDwHwsCjZdRw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/886/1*F4boA6Z10qzeJq4cpmp4jw.png" /></figure><p>We can clearly see how coefficients are getting closer and closer to zero (thought never actually reach it) as their importance in the model drops</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e7441b2302318f65b8421866c5fc7504/href">https://medium.com/media/e7441b2302318f65b8421866c5fc7504/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u8lR_FDvPV6rH0JqJR87sw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/886/1*Q9hvolN8hDV0xTZwlIBR2g.png" /></figure><p>Lasso regression turned out to be more conservative and removed 23-rd lag from most important features (and also dropped 5 features completely) which only made the quality of prediction better.</p><h3>Boosting</h3><p>Why not try XGBoost now?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/0*4XXSSYy4nYDDgNex.jpg" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2c1862cf66343cc5b15da9f32bbc4a98/href">https://medium.com/media/2c1862cf66343cc5b15da9f32bbc4a98/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dpguJye_sOtOFK746JVovg.png" /></figure><p>Here is the winner! The smallest error on the test set among all the models we’ve tried so far.</p><p>Yet this victory is decieving and it might not be the brightest idea to fit xgboost as soon as you get your hands over time series data. Generally tree-based models poorly handle trends in data, compared to linear models, so you have to detrend your series first or use some tricks to make the magic happen. Ideally — make the series stationary and then use XGBoost, for example, you can forecast trend separately with a linear model and then add predictions from xgboost to get final forecast.</p><h3>Conclusion</h3><p>We got acquainted with different time series analysis and prediction methods and approaches. Unfortunately, or maybe luckily, there’s no silver bullet to solve this kind of problems. Methods developed in the 60s of the last century (and some even in the beginning of the XIX century) are still popular along with the LSTM and RNN (not covered in this article). Partially this is related to the fact that the prediction task as any other data related task is creative in so many aspects and definitely requires research. In spite of the large number of formal quality metrics and approaches to parameters estimation, it’s often required to seek and try something different for each time series. Last but not least the balance between quality and cost is important. As a good example SARIMA model mentioned here not once or twice can produce spectacular results after due tuning but might require many hours of tambourine dancing time series manipulation, as in the same time simple linear regression model can be build in 10 minutes giving more or less comparable results.</p><figure><a href="http://buymeacoffee.com/datawondering"><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-X5BvfVa0fkYPrLm.png" /></a></figure><p>My socials:</p><ul><li><a href="https://substack.com/@datawondering">Data Wondering blog [English]</a></li><li><a href="https://t.me/data_wondering">Data Wondering blog [Russian]</a></li></ul><h3>Useful resources</h3><ul><li><a href="https://people.duke.edu/~rnau/411home.htm">Online textbook</a> of the advanced statistical forecasting course of the Duke University — covers in details various smoothing techniques, linear and ARIMA models</li><li><a href="https://bmcbioinformatics.biomedcentral.com/articles/10.1186/1471-2105-15-276">Comparison of ARIMA and Random Forest time series models for prediction of avian influenza H5N1 outbreaks</a> — one of a few where random forest applicability to the tasks of time series forecasting is actively defended</li><li><a href="http://www.blackarbs.com/blog/time-series-analysis-in-python-linear-models-to-garch/11/1/2016">Time Series Analysis (TSA) in Python — Linear Models to GARCH</a> ARIMA models family and their applicability to the task of modeling financial indicators (Brian Christopher)</li></ul><p><em>Author: </em><a href="https://github.com/DmitrySerg"><em>Dmitry Sergeyev</em></a><em>. Translated and edited by </em><a href="https://www.linkedin.com/in/borowis/"><em>Borys Zibrov</em></a><em>, and </em><a href="https://www.linkedin.com/in/yuanyuanpao/"><em>Yuanyuan Pao</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a270cb05e0b3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-9-time-series-analysis-in-python-a270cb05e0b3">Open Machine Learning Course. Topic 9. Part 1. Time series analysis in Python</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 8. Vowpal Wabbit: Fast Learning with Gigabytes of Data]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-8-vowpal-wabbit-fast-learning-with-gigabytes-of-data-60f750086237?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/60f750086237</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[algorithms]]></category>
            <category><![CDATA[education]]></category>
            <dc:creator><![CDATA[Yury Kashnitsky]]></dc:creator>
            <pubDate>Mon, 02 Apr 2018 10:55:38 GMT</pubDate>
            <atom:updated>2018-09-19T09:11:16.519Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*OYzZCTaTs41J4NyTTjBdHw.png" /></figure><p>This week, we’ll cover two reasons for Vowpal Wabbit’s exceptional training speed, namely, online learning and hashing trick, in both theory and practice. We will try it out with news, movie reviews, and StackOverflow questions.</p><h3>Outline</h3><ol><li>Stochastic gradient descent and online learning</li></ol><ul><li>1.1. SGD</li><li>1.2. Online approach to learning</li></ul><p>2. Categorical data processing: Label Encoding, One-Hot Encoding, Hashing trick</p><ul><li>2.1. Label Encoding</li><li>2.2. One-Hot Encoding</li><li>2.3. Hashing trick</li></ul><p>3. Vowpal Wabbit</p><ul><li>3.1. News. Binary classification</li><li>3.2. News. Multiclass classification</li><li>3.3. IMDB reviews</li><li>3.4. Classifying gigabytes of StackOverflow questions</li></ul><p>4. Assignment #8</p><p>5. Useful resources</p><p><em>The following content is better viewed and reproduced as a </em><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic08_sgd_hashing_vowpal_wabbit/topic8_sgd_hashing_vowpal_wabbit.ipynb?flush_cache=true"><em>Jupyter-notebook</em></a><em> or a </em><a href="https://www.kaggle.com/kashnitsky/vowpal-wabbit-tutorial-blazingly-fast-learning"><em>Kaggle kernel</em></a><em>.</em></p><h3>1. Stochastic gradient descent and online learning</h3><h3>1.1. Stochastic gradient descent</h3><p>Despite the fact that gradient descent is one of the first things learned in machine learning and optimization courses, it is one of its modifications, Stochastic Gradient Descent (SGD), that is hard to top.</p><p>Recall that the idea of gradient descent is to minimize some function by making small steps in the direction of the fastest decrease. This method was named due to the following fact from calculus: vector</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/336/1*VOJCm7l4-nm1bOnxBWf_pw.png" /></figure><p>of partial derivatives of the function f(x) = f(x1, …, xn) points to the direction of the fastest function growth. It means that, by moving in the opposite direction (antigradient), it is possible to decrease the function value with the fastest rate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tTTrP_gLIMONG55t.jpg" /><figcaption>A snowboarder (me) in Sheregesh, Russia’s most popular winter resort. Highly recommended it if you like skiing or snowboarding.</figcaption></figure><p>In addition to advertising beautiful landscapes, this picture depicts the idea of gradient descent. If you want to ride as fast as possible, you need to choose the path of steepest descent. Calculating antigradients can be seen as evaluating the slope at various spots.</p><p><strong>Example</strong></p><p>The paired regression problem can be solved with gradient descent. Let us predict one variable using another: height with weight. Assume that these variables are linearly dependent. We will use the <a href="http://wiki.stat.ucla.edu/socr/index.php/SOCR_Data">SOCR</a> dataset.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2f3f305f3fccf2b03c3f389bd62ec880/href">https://medium.com/media/2f3f305f3fccf2b03c3f389bd62ec880/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ea80c6b8cb6cbf04e81cc15dc3238c34/href">https://medium.com/media/ea80c6b8cb6cbf04e81cc15dc3238c34/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/381/1*8W7KT3ApZhiuu9GqRFUSVQ.png" /></figure><p>Here we have a vector <em>x </em>of dimension ℓ (weight of every person i.e. training sample) and <em>y</em>, a vector containing the height of every person in the dataset.</p><p>The task is the following: find weights <em>w0</em> and <em>w1</em> such that predicting height as <em>yi = w0 + w_1 xi</em> (where <em>yi</em> is <em>i</em>-th height value, <em>xi</em> is <em>i</em>-th weight value) minimizes the squared error (as well as mean squared error since 1/ℓ doesn’t make any difference):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m0ZHOM7spOMZUICtenyxfQ.png" /></figure><p>We will use gradient descent, utilizing the partial derivatives of <em>SE(w0,w1)</em> over weights <em>w0</em> and <em>w1</em>. An iterative training procedure is then defined by simple update formulas (we change model weights in small steps, proportional to a small constant <em>η</em>, towards the antigradient of the function <em>SE(w0, w1))</em>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/485/1*EDPZnoeEi7C4kIW1fBp72w.png" /></figure><p>Computing the partial derivatives, we get the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/598/1*395VXoPthjuRf3AVO7Z6fQ.png" /></figure><p>This math works quite well as long as the amount of data is not large (we will not discuss issues with local minima, saddle points, choosing the learning rate, moments and other stuff – these topics are covered very thoroughly in <a href="http://www.deeplearningbook.org/contents/numerical.html">the Numeric Computation chapter</a> in “Deep Learning”). There is an issue with batch gradient descent — the gradient evaluation requires the summation of a number of values for every object from the training set. In other words, the algorithm requires a lot of iterations, and every iteration recomputes weights with formula which contains a sum over the whole training set. What happens when we have billions of training samples?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*HhCeBJdPY4OH1my-.jpeg" /></figure><p>Hence the motivation for stochastic gradient descent! Simply put, we throw away the summation sign and update the weights only over single training samples (or a small number of them). In our case, we have the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/513/1*Hk42uEbVu1MbqbUBbyZWEQ.png" /></figure><p>With this approach, there is no guarantee that we will move in best possible direction at every iteration. Therefore, we may need many more iterations, but we get much faster weight updates.</p><p>Andrew Ng has a good illustration of this in his <a href="https://www.coursera.org/learn/machine-learning">machine learning course</a>. Let’s take a look.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/388/0*o692cOFyNxfJzYSL.png" /></figure><p>These are the contour plots for some function, and we want to find the global minimum of this function. The red curve shows weight changes (in this picture, <em>θ0</em> and <em>θ1</em> correspond to our <em>w0</em> and <em>w1</em>). According to the properties of a gradient, the direction of change at every point is orthogonal to contour plots. With stochastic gradient descent, weights are changing in a less predictable manner, and it even may seem that some steps are wrong by leading away from minima; however, both procedures converge to the same solution.</p><h3>1.2. Online approach to learning</h3><p>Stochastic gradient descent gives us practical guidance for training both classifiers and regressors with large amounts of data up to hundreds of GBs (depending on computational resources).</p><p>Considering the case of paired regression, we can store the training data set <em>(X,y)</em> in HDD without loading it into RAM (where it simply won’t fit), read objects one by one, and update the weights of our model:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/513/1*Hk42uEbVu1MbqbUBbyZWEQ.png" /></figure><p>After working through the whole training dataset, our loss function (for example, quadratic squared root error in regression or logistic loss in classification) will decrease, but it usually takes dozens of passes over the training set to make the loss small enough.</p><p>This approach to learning is called <strong>online learning</strong>, and this name emerged even before machine learning MOOC-s turned mainstream.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/437/1*xuawoa5-SB2JAxOxVtLmrA.jpeg" /></figure><p>We did not discuss many specifics about SGD here. If you want dive into theory, I highly recommend <a href="https://www.amazon.com/Convex-Optimization-Stephen-Boyd/dp/0521833787">“Convex Optimization” by Stephen Boyd</a>. Now, we will introduce the Vowpal Wabbit library, which is good for training simple models with huge data sets thanks to stochastic optimization and another trick, feature hashing.</p><p>In scikit-learn, classifiers and regressors trained with SGD are named SGDClassifier and SGDRegressor in sklearn.linear_model. These are nice implementations of SGD, but we&#39;ll focus on VW since it is more performant than sklearn&#39;s SGD models in many aspects.</p><h3>2. Categorical feature processing: Label Encoding, One-Hot Encoding, and Hashing trick</h3><p>Many classification and regression algorithms operate in Euclidean or metric space, implying that data is represented with vectors of real numbers. However, in real data, we often have categorical features with discrete values such as yes/no or January/February/…/December. We will see how to process this kind of data, particularly with linear models, and how to deal with many categorial features even when they have many unique values.</p><p>Let’s explore the <a href="https://archive.ics.uci.edu/ml/datasets/bank+marketing">UCI bank marketing dataset</a> where most of features are categorial. Actually, more features are present in this dataset, explore <a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic08_sgd_hashing_vowpal_wabbit/topic8_sgd_hashing_vowpal_wabbit.ipynb?flush_cache=true">this</a> Jupyter notebook.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/32d2e16419d4f21256b402cfe0e5170b/href">https://medium.com/media/32d2e16419d4f21256b402cfe0e5170b/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-PNu9d3xC1gZi6M7l-eNrA.png" /></figure><p>We can see that most of features are not represented by numbers. This poses a problem because we cannot use most machine learning methods (at least those implemented in scikit-learn) out-of-the-box.</p><p>Let’s dive into the “education” feature.</p><pre>df[&#39;education&#39;].value_counts().plot.barh();</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/443/1*qQ9uuhYJYeLDGONr4n6S3g.png" /></figure><p>The most straightforward solution is to map each value of this feature into a unique number. For example, we can map university.degree to 0, basic.9y to 1, and so on. You can use sklearn.preprocessing.LabelEncoder to perform this mapping.</p><pre>label_encoder = LabelEncoder()</pre><p>The fit method of this class finds all unique values and builds the actual mapping between categories and numbers, and the transform method converts the categories into numbers. After fit is executed, label_encoder will have the classes_ attribute with all unique values of the feature. Let us count them to make sure the transformation was correct.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3a2c5d6e3ed2ebd79c8c54b7d9dd4863/href">https://medium.com/media/3a2c5d6e3ed2ebd79c8c54b7d9dd4863/href</a></iframe><pre># Output:<br> <br>{0: &#39;basic.4y&#39;, 1: &#39;basic.6y&#39;, 2: &#39;basic.9y&#39;, 3: &#39;high.school&#39;, 4: &#39;illiterate&#39;, 5: &#39;professional.course&#39;, 6: &#39;university.degree&#39;, 7: &#39;unknown&#39;}</pre><pre>df[&#39;education&#39;] = mapped_education df.head()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LiRc1fhg7Rtd3bx1MxrMHQ.png" /></figure><p>Let’s apply the transformation to other columns of type object.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/efd03e7666cc944267511390b950715c/href">https://medium.com/media/efd03e7666cc944267511390b950715c/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oLYzIbspnplCxuedwnYOZQ.png" /></figure><p>The main issue with this approach is that we have now introduced some relative ordering where it might not exist.</p><p>For example, we implicitly introduced algebra over the values of the job feature where we can now substract the job of client #2 from the job of client #1 :</p><pre>df.loc[1].job - df.loc[2].job<br># -1.0</pre><p>Does this operation make any sense? Not really. Let’s try to train logisitic regression with this feature transformation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/24a1afa55e36742957d5b9260866e5b7/href">https://medium.com/media/24a1afa55e36742957d5b9260866e5b7/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QWZq5rCEPFREdnYOa48ipA.png" /></figure><p>We can see that logistic regression never predicts class 1. In order to use linear models with categorial features, we will use a different approach: One-Hot Encoding.</p><h3>2.2. One-Hot Encoding</h3><p>Suppose that some feature can have one of 10 unique values. One-hot encoding creates 10 new features corresponding to these unique values, all of them <em>except one</em> are zeros.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/95fabedc19bf0dab86abef8daa7c6288/href">https://medium.com/media/95fabedc19bf0dab86abef8daa7c6288/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/339/1*004LK2vgvRblVTx2dn164Q.png" /></figure><p>This idea is implemented in the OneHotEncoder class from sklearn.preprocessing. By default OneHotEncoder transforms data into a sparse matrix to save memory space because most of the values are zeroes and because we do not want to take up more RAM. However, in this particular example, we do not encounter such problems, so we are going to use a &quot;dense&quot; matrix representation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c38d11ada7b60024e0a43d78f43f04f1/href">https://medium.com/media/c38d11ada7b60024e0a43d78f43f04f1/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/749/1*3aV37glp1NSghP0o-H5HPA.png" /></figure><p>We have 53 columns that correspond to the number of unique values of categorical features in our data set. When transformed with One-Hot Encoding, this data can be used with linear models:</p><pre>print(logistic_regression_accuracy_on(encoded_categorical_columns,  labels))</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/545/1*WRZth-h2pxxLRGO7GBuRfw.png" /></figure><h3>2.3. Hashing trick</h3><p>Real data can be volatile, meaning we cannot guarantee that new values of categorial features will not occur. This issue hampers using a trained model on new data. Besides that, LabelEncoder requires preliminary analysis of the whole dataset and storage of constructed mappings in memory, which makes it difficult to work with large datasets.</p><p>There is a simple approach to vectorization of categorial data based on hashing and is known as, not-so-surprisingly, the hashing trick.</p><p>Hash functions can help us find unique codes for different feature values, for example:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1956e90f1e7f9b08d6c7dac4928125bc/href">https://medium.com/media/1956e90f1e7f9b08d6c7dac4928125bc/href</a></iframe><pre># university.degree -&gt; -6241459093488141593<br># high.school -&gt; 7728198035707179500<br># illiterate -&gt; -7360093633803373451</pre><p>We will not use negative values or values of high magnitude, so we restrict the range of values for the hash function:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2650d9a3533670802189dcbe44a7b6ea/href">https://medium.com/media/2650d9a3533670802189dcbe44a7b6ea/href</a></iframe><pre># university.degree -&gt; 7<br># high.school -&gt; 0<br># illiterate -&gt; 24</pre><p>Imagine that our data set contains a single (i.e. not married) student, who received a call on Monday. His feature vectors will be created similarly as in the case of One-Hot Encoding but in the space with fixed range for all features:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d6b26af94b4d838789fc7ddb092c07b1/href">https://medium.com/media/d6b26af94b4d838789fc7ddb092c07b1/href</a></iframe><pre># job=student -&gt; 20<br># marital=single -&gt; 23<br># day_of_week=mon -&gt; 9</pre><p>We want to point out that we hash not only feature values but also pairs of <strong>feature name + feature value</strong>. It is important to do this so that we can distinguish the same values of different features.</p><p>Is it possible to have a collision when using hash codes? Sure, it is possible, but it is a rare case with large enough hashing spaces. Even if collision occurs, regression or classification metrics will not suffer much. In this case, hash collisions work as a form of regularization.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/496/0*aWJVQkWcJukZKIqn.jpeg" /></figure><p>You may be saying “WTF?”; hashing seems counterintuitive. This is true, but these heuristics sometimes are, in fact, the only plausible approach to work with categorial data. Moreover, this technique has proven to just work. As you work more with data, you may see this for yourself.</p><h3>3. Vowpal Wabbit</h3><p><a href="https://github.com/JohnLangford/vowpal_wabbit">Vowpal Wabbit</a> (VW) is one of the most widespread machine learning libraries used in industry. It is prominent for its training speed and support of many training modes, especially for online learning with big and high-dimentional data. This is one of the major merits of the library. Also, with the hashing trick implemented, Vowpal Wabbit is a perfect choice for working with text data.</p><p>Shell is the main interface for VW. Try this command:</p><p>!vw — help</p><p>Vowpal Wabbit reads data from files or from standard input stream (stdin) with the following format:</p><p>[Label] [Importance] [Tag]|Namespace Features |Namespace Features ... |Namespace Features</p><p>Namespace=String[:Value]</p><p>Features=(String[:Value] )*</p><p>here [] denotes non-mandatory elements, and (…)* means multiple inputs allowed.</p><ul><li><strong>Label</strong> is a number. In the case of classification, it is usually 1 and -1; for regression, it is a real float value</li><li><strong>Importance</strong> is a number. It denotes the sample weight during training. Setting this helps when working with imbalanced data.</li><li><strong>Tag</strong> is a string without spaces. It is the “name” of the sample that VW saves upon prediction. In order to separate Tag from Importance, it is better to start Tag with the ‘ character.</li><li><strong>Namespace</strong> is for creating different feature spaces.</li><li><strong>Features</strong> are object features inside a given <strong>Namespace</strong>. Features have weight 1.0 by default, but it can be changed, for example feature:0.1.</li></ul><p>The following string matches the VW format:</p><pre>1 1.0 |Subject WHAT car is this |Organization University of Maryland:0.5 College Park</pre><p>Let’s check the format by running VW with this training sample:</p><pre>! echo &#39;1 1.0 |Subject WHAT car is this |Organization University of Maryland:0.5 College Park&#39; | vw</pre><pre># Output:</pre><pre>Num weight bits = 18<br>learning rate = 0.5<br>initial_t = 0<br>power_t = 0.5<br>using no cache<br>Reading datafile = <br>num sources = 1<br>average  since         example        example  current  current  current<br>loss     last          counter         weight    label  predict features<br>1.000000 1.000000            1            1.0   1.0000   0.0000       10<br><br>finished run<br>number of examples per pass = 1<br>passes used = 1<br>weighted example sum = 1.000000<br>weighted label sum = 1.000000<br>average loss = 1.000000<br>best constant = 1.000000<br>best constant&#39;s loss = 0.000000<br>total feature number = 10</pre><p>VW is a wonderful tool for working with text data. We’ll illustrate it with the <a href="http://scikit-learn.org/stable/datasets/twenty_newsgroups.html">20newsgroups dataset</a>, which contains letters from 20 different newsletters.</p><h3>3.1. News. Binary classification.</h3><pre># load data with sklearn&#39;s function<br>newsgroups = fetch_20newsgroups(PATH_TO_ALL_DATA)<br>newsgroups[&#39;target_names&#39;]</pre><p>The 20 topics are [&#39;alt.atheism&#39;, &#39;comp.graphics&#39;, &#39;comp.os.ms-windows.misc&#39;, &#39;comp.sys.ibm.pc.hardware&#39;, &#39;comp.sys.mac.hardware&#39;, &#39;comp.windows.x&#39;, &#39;misc.forsale&#39;, &#39;rec.autos&#39;, &#39;rec.motorcycles&#39;, &#39;rec.sport.baseball&#39;, &#39;rec.sport.hockey&#39;, &#39;sci.crypt&#39;, &#39;sci.electronics&#39;, &#39;sci.med&#39;, &#39;sci.space&#39;, &#39;soc.religion.christian&#39;, &#39;talk.politics.guns&#39;, &#39;talk.politics.mideast&#39;, &#39;talk.politics.misc&#39;, &#39;talk.religion.misc&#39;]</p><p>Lets look at the first document in this collection:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/394b087cb907c3e2f3375b4092a142f3/href">https://medium.com/media/394b087cb907c3e2f3375b4092a142f3/href</a></iframe><pre># Output:</pre><pre>-----<br>rec.autos<br>-----<br>From: lerxst@wam.umd.edu (where&#39;s my thing)<br>Subject: WHAT car is this!?<br>Nntp-Posting-Host: rac3.wam.umd.edu<br>Organization: University of Maryland, College Park<br>Lines: 15<br><br> I was wondering if anyone out there could enlighten me on this car I saw<br>the other day. It was a 2-door sports car, looked to be from the late 60s/<br>early 70s. It was called a Bricklin. The doors were really small. In addition,<br>the front bumper was separate from the rest of the body. This is <br>all I know. If anyone can tellme a model name, engine specs, years<br>of production, where this car is made, history, or whatever info you<br>have on this funky looking car, please e-mail.<br><br>Thanks,<br>- IL<br>   ---- brought to you by your neighborhood Lerxst ----<br>----</pre><p>Now we convert the data into something Vowpal Wabbit can understand. We will throw away words shorter than 3 symbols. Here, we will skip some important NLP stages such as stemming and lemmatization; however, we will later see that VW solves the problem even without these steps.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2f16a87e3c281e87692afb695c31174a/href">https://medium.com/media/2f16a87e3c281e87692afb695c31174a/href</a></iframe><pre># Output:</pre><pre>&#39;1 |text from lerxst wam umd edu where thing subject what car this nntp posting host rac3 wam umd edu organization university maryland college park lines was wondering anyone out there could enlighten this car saw the other day was door sports car looked from the late 60s early 70s was called bricklin the doors were really small addition the front bumper was separate from the rest the body this all know anyone can tellme model name engine specs years production where this car made history whatever info you have this funky looking car please mail thanks brought you your neighborhood lerxst\n&#39;</pre><p>We split the dataset into train and test and write these into separate files. We will consider a document as positive if it corresponds to <strong>rec.autos</strong>. Thus, we are constructing a model which distinguishes articles about cars from other topics:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/99d5ddefb5037b33ccb05ba708c62e1f/href">https://medium.com/media/99d5ddefb5037b33ccb05ba708c62e1f/href</a></iframe><p>Now, we pass the created training file to Vowpal Wabbit. We solve the classification problem with a hinge loss function (linear SVM). The trained model will be saved in the 20news_model.vw file:</p><pre>!vw -d $PATH_TO_ALL_DATA/20news_train.vw \ --loss_function hinge -f $PATH_TO_ALL_DATA/20news_model.vw</pre><pre># Output:</pre><pre>final_regressor = ../../data//20news_model.vw<br>Num weight bits = 18<br>learning rate = 0.5<br>initial_t = 0<br>power_t = 0.5<br>using no cache<br>Reading datafile = ../../data//20news_train.vw<br>num sources = 1<br>average  since         example        example  current  current  current<br>loss     last          counter         weight    label  predict features<br>1.000000 1.000000            1            1.0  -1.0000   0.0000      157<br>0.911276 0.822551            2            2.0  -1.0000  -0.1774      159<br>0.605793 0.300311            4            4.0  -1.0000  -0.3994       92<br>0.419594 0.233394            8            8.0  -1.0000  -0.8167      129<br>0.313998 0.208402           16           16.0  -1.0000  -0.6509      108<br>0.196014 0.078029           32           32.0  -1.0000  -1.0000      115<br>0.183158 0.170302           64           64.0  -1.0000  -0.7072      114<br>0.261046 0.338935          128          128.0   1.0000  -0.7900      110<br>0.262910 0.264774          256          256.0  -1.0000  -0.6425       44<br>0.216663 0.170415          512          512.0  -1.0000  -1.0000      160<br>0.176710 0.136757         1024         1024.0  -1.0000  -1.0000      194<br>0.134541 0.092371         2048         2048.0  -1.0000  -1.0000      438<br>0.104403 0.074266         4096         4096.0  -1.0000  -1.0000      644<br>0.081329 0.058255         8192         8192.0  -1.0000  -1.0000      174<br><br>finished run<br>number of examples per pass = 8485<br>passes used = 1<br>weighted example sum = 8485.000000<br>weighted label sum = -7555.000000<br>average loss = 0.079837<br>best constant = -1.000000<br>best constant&#39;s loss = 0.109605<br>total feature number = 2048932</pre><p>VW prints a lot of interesting info while training (one can suppress it with the --quiet parameter). You can see documentation of the diagnostic output on <a href="https://github.com/JohnLangford/vowpal_wabbit/wiki/Tutorial#vws-diagnostic-information">GitHub</a>. Note how average loss drops while training. For loss computation, VW uses samples it has never seen before, so this measure is usually accurate. Now, we apply our trained model to the test set, saving predictions into a file with the -p flag:</p><pre>!vw -i $PATH_TO_ALL_DATA/20news_model.vw -t -d $PATH_TO_ALL_DATA/20news_test.vw \ -p $PATH_TO_ALL_DATA/20news_test_predictions.txt</pre><p>Now we load our predictions, compute AUC, and plot the ROC curve:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7f0a50cc67a2e090a4e5233cc6f85c9d/href">https://medium.com/media/7f0a50cc67a2e090a4e5233cc6f85c9d/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/402/1*9cmeLXu__qM6Uw9Ju1oMgg.png" /></figure><p>The AUC value we get shows that we have achieved high classification quality.</p><h3>3.2. News. Multiclass classification</h3><p>We will use the same news dataset, but, this time, we will solve a multiclass classification problem. Vowpal Wabbit is a little picky – it wants labels starting from 1 till K, where K – is the number of classes in the classification task (20 in our case). So we will use LabelEncoder and add 1 afterwards (recall that LabelEncoder maps labels into range from 0 to K-1).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5f6422f5bea335ab95879bb585e7ee66/href">https://medium.com/media/5f6422f5bea335ab95879bb585e7ee66/href</a></iframe><p>The data is the same, but we have changed the labels, train_labels_mult and test_labels_mult, into label vectors from 1 to 20.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a9bd37a495dc62be5a5f90337698727f/href">https://medium.com/media/a9bd37a495dc62be5a5f90337698727f/href</a></iframe><p>We train Vowpal Wabbit in multiclass classification mode, passing the oaa parameter(&quot;one against all&quot;) with the number of classes. Also, let&#39;s see what parameters our model quality is dependent on (more info can be found in the <a href="https://github.com/JohnLangford/vowpal_wabbit/wiki/Tutorial">official Vowpal Wabbit tutorial</a>):</p><ul><li>learning rate (-l, 0.5 default) — rate of weight change on every step</li><li>learning rate decay ( — power_t, 0.5 default) — it is proven in practice, that, if the learning rate drops with the number of steps in stochastic gradient descent, we approach the minimum loss better</li><li>loss function ( — loss_function) — the entire training algorithm depends on it. See <a href="https://github.com/JohnLangford/vowpal_wabbit/wiki/Loss-functions">docs</a> for loss functions</li><li>Regularization (-l1) — note that VW calculates regularization for every object. That is why we usually set regularization values to about 1e-20.</li></ul><p>Additionally, we can try automatic Vowpal Wabbit parameter tuning with <a href="https://github.com/hyperopt/hyperopt">Hyperopt</a>.</p><pre>!vw — oaa 20 $PATH_TO_ALL_DATA/20news_train_mult.vw -f $PATH_TO_ALL_DATA/20news_model_mult.vw \<br> — loss_function=hinge</pre><pre>!vw -i $PATH_TO_ALL_DATA/20news_model_mult.vw -t -d $PATH_TO_ALL_DATA/20news_test_mult.vw \<br>-p $PATH_TO_ALL_DATA/20news_test_predictions_mult.txt</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/16fd124b365c00cd53aeade89a2c3d3a/href">https://medium.com/media/16fd124b365c00cd53aeade89a2c3d3a/href</a></iframe><p>Here is how often the model misclassifies atheism with other topics:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/830f22705405e5b6f0b19b639413be1b/href">https://medium.com/media/830f22705405e5b6f0b19b639413be1b/href</a></iframe><pre># Output:</pre><pre>rec.autos 1<br>rec.sport.baseball 1<br>sci.med 1<br>soc.religion.christian 3<br>talk.religion.misc 5</pre><h3>3.3. IMDB movie reviews</h3><p>In this part we will do binary classification of <a href="http://www.imdb.com/">IMDB</a> (International Movie DataBase) movie reviews. We will see how fast Vowpal Wabbit performs.</p><p>Using the load_files function from sklearn.datasets, we load the movie reviews <a href="https://drive.google.com/file/d/1xq4l5c0JrcxJdyBwJWvy0u9Ad_pvkJ1l/view?usp=sharing">here</a>. If you want to reproduce the results, please download the archive, unzip it, and set the path to imdb_reviews (it already contains <em>train</em> and <em>test</em> subdirectories). Unpacking can take several minutes as there are 100k files. Both train and test sets hold 12.5k good and bad movie reviews. First, we split the texts and labels.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a1309bd6e1b73f72aa7ce78edebe4105/href">https://medium.com/media/a1309bd6e1b73f72aa7ce78edebe4105/href</a></iframe><p>Take a look at examples of reviews and their corresponding labels.</p><pre>text_train[0]</pre><pre># Output:<br>b&quot;Zero Day leads you to think, even re-think why two boys/young men would do what they did - commit mutual suicide via slaughtering their classmates. It captures what must be beyond a bizarre mode of being for two humans who have decided to withdraw from common civility in order to define their own/mutual world via coupled destruction.&lt;br /&gt;&lt;br /&gt;It is not a perfect movie but given what money/time the filmmaker and actors had - it is a remarkable product. In terms of explaining the motives and actions of the two young suicide/murderers it is better than &#39;Elephant&#39; - in terms of being a film that gets under our &#39;rationalistic&#39; skin it is a far, far better film than almost anything you are likely to see. &lt;br /&gt;&lt;br /&gt;Flawed but honest with a terrible honesty.&quot;</pre><pre>y_train[0] # good review</pre><pre># Output:<br>1</pre><pre>text_train[1]</pre><pre># Output:<br>b&#39;Words can\&#39;t describe how bad this movie is. I can\&#39;t explain it by writing only. You have too see it for yourself to get at grip of how horrible a movie really can be. Not that I recommend you to do that. There are so many clich\xc3\xa9s, mistakes (and all other negative things you can imagine) here that will just make you cry. To start with the technical first, there are a LOT of mistakes regarding the airplane. I won\&#39;t list them here, but just mention the coloring of the plane. They didn\&#39;t even manage to show an airliner in the colors of a fictional airline, but instead used a 747 painted in the original Boeing livery. Very bad. The plot is stupid and has been done many times before, only much, much better. There are so many ridiculous moments here that i lost count of it really early. Also, I was on the bad guys\&#39; side all the time in the movie, because the good guys were so stupid. &quot;Executive Decision&quot; should without a doubt be you\&#39;re choice over this one, even the &quot;Turbulence&quot;-movies are better. In fact, every other movie in the world is better than this one.&#39;</pre><pre>y_train[1] # bad review</pre><pre># Output:<br>0</pre><p>Now, we prepare training (movie_reviews_train.vw), validation (movie_reviews_valid.vw), and test (movie_reviews_test.vw) sets for Vowpal Wabbit. We will use 70% for training, 30% for the hold-out set.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8f6d550aae128e510de5d682eaa3c389/href">https://medium.com/media/8f6d550aae128e510de5d682eaa3c389/href</a></iframe><p>Now, launch Vowpal Wabbit with the following arguments:</p><ul><li>-d, path to training set (corresponding .vw file)</li><li>— loss_function — hinge (feel free to experiment here)</li><li>-f — path to the output file (which can also be in the .vw format)</li></ul><pre>!vw -d $PATH_TO_ALL_DATA/movie_reviews_train.vw --loss_function hinge -f $PATH_TO_ALL_DATA/movie_reviews_model.vw --quiet</pre><p>Next, make the hold-out prediction with the following VW arguments:</p><ul><li>-i –path to the trained model (.vw file)</li><li>-t -d — path to hold-out set (.vw file)</li><li>-p — path to a txt-file where the predictions will be stored</li></ul><pre>!vw -i $PATH_TO_ALL_DATA/movie_reviews_model.vw -t \ <br>-d $PATH_TO_ALL_DATA/movie_reviews_valid.vw -p $PATH_TO_ALL_DATA/movie_valid_pred.txt --quiet</pre><p>Read the predictions from the text file and estimate the accuracy and ROC AUC. Note that VW prints probability estimates of the +1 class. These estimates are distributed from -1 to 1, so we can convert these into binary answers, assuming that positive values belong to class 1.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cb18f181febb81e877283f67b227cc92/href">https://medium.com/media/cb18f181febb81e877283f67b227cc92/href</a></iframe><pre>Accuracy: 0.885<br>AUC: 0.942</pre><p>Again, do the same for the test set.</p><pre>!vw -i $PATH_TO_ALL_DATA/movie_reviews_model.vw -t \ -d $PATH_TO_ALL_DATA/movie_reviews_test.vw \ -p $PATH_TO_ALL_DATA/movie_test_pred.txt --quiet</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/88ee1e50024bdab151b0275a7951936d/href">https://medium.com/media/88ee1e50024bdab151b0275a7951936d/href</a></iframe><pre>Accuracy: 0.88<br>AUC: 0.94</pre><p>Let’s try to achieve a higher accuracy by incorporating bigrams.</p><pre>!vw -d $PATH_TO_ALL_DATA/movie_reviews_train.vw \ --loss_function hinge --ngram 2 -f $PATH_TO_ALL_DATA/movie_reviews_model2.vw --quiet</pre><pre>!vw -i$PATH_TO_ALL_DATA/movie_reviews_model2.vw -t -d $PATH_TO_ALL_DATA/movie_reviews_valid.vw \ -p $PATH_TO_ALL_DATA/movie_valid_pred2.txt --quiet</pre><pre>!vw -i $PATH_TO_ALL_DATA/movie_reviews_model2.vw -t -d $PATH_TO_ALL_DATA/movie_reviews_test.vw \ -p $PATH_TO_ALL_DATA/movie_test_pred2.txt --quiet</pre><p>Adding bigrams really helped to improve our model!</p><pre># Validation <br>Accuracy: 0.894<br>AUC: 0.954</pre><pre># Test<br>Accuracy: 0.888<br>AUC: 0.952</pre><h3>3.4. Classifying gigabytes of StackOverflow questions</h3><p>Now, let’s see Vowpal Wabbit work on large datasets. There is a 10GB dataset of StackOverflow questions <a href="https://drive.google.com/file/d/1ZU4J3KhJDrHVMj48fROFcTsTZKorPGlG/view?usp=sharing">here</a>. The original dataset is comprised of 10 million questions; each question can have several tags. The data is quite clean, so don’t call it “Big Data”, even in a pub. :)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/638/0*Z0bVkk0iTYNOaD5I.jpg" /></figure><p>We chose only 10 tags: ‘javascript’, ‘java’, ‘python’, ‘ruby’, ‘php’, ‘c++’, ‘c#’, ‘go’, ‘scala’ and ‘swift’. Let’s solve the 10-class classification problem: we want to predict a tag corresponding to one of these 10 popular programming languages given only the text of the question. Your task in assignment is to</p><pre># change the path to data <br>PATH_TO_STACKOVERFLOW_DATA = &#39;stackoverflow_10mln&#39;</pre><pre># Print the first 3 lines from a sample of the dataset.</pre><pre>!head -3 $PATH_TO_STACKOVERFLOW_DATA/stackoverflow_train.vw</pre><pre># Output:</pre><pre>1 | i ve got some code in window scroll that checks if an element is visible then triggers another function however only the first section of code is firing both bits of code work in and of themselves if i swap their order whichever is on top fires correctly my code is as follows fn isonscreen function use strict var win window viewport top win scrolltop left win scrollleft bounds this offset viewport right viewport left + win width viewport bottom viewport top + win height bounds right bounds left + this outerwidth bounds bottom bounds top + this outerheight return viewport right lt bounds left viewport left gt bounds right viewport bottom lt bounds top viewport top gt bounds bottom window scroll function use strict var load_more_results ajax load_more_results isonscreen if load_more_results true loadmoreresults var load_more_staff ajax load_more_staff isonscreen if load_more_staff true loadmorestaff what am i doing wrong can you only fire one event from window scroll i assume not <br>4 | redefining some constant in ruby ex foo bar generates the warning already initialized constant i m trying to write a sort of reallyconstants module where this code should have this behaviour reallyconstants define_constant foo bar gt sets the constant reallyconstants foo to bar reallyconstants foo gt bar reallyconstants foo foobar gt this should raise an exception that is constant redefinition should generate an exception is that possible 1 | in my form panel i added a checkbox setting stateful true stateid loginpanelremeberme then before sending form i save state calling this savestate on the panel all other componenets save their state and whe i reload the page they recall the previous state but checkbox alway start in unchecked state is there any way to force save value</pre><p>After selecting our 10 tags, we have a 4.7G dataset that we divide into train and test.</p><pre>!du -hs $PATH_TO_STACKOVERFLOW_DATA/stackoverflow_*.vw</pre><pre># Output:</pre><pre>4,7G	<br>stackoverflow_10mln.vw <br>1,6G	<br>stackoverflow_test.vw <br>3,1G	<br>stackoverflow_train.vw</pre><p>We will process the training set (3.1 GiB) with Vowpal Wabbit and the following arguments:</p><ul><li>-oaa 10 — for multiclass classification with 10 classes</li><li>-d — path to data</li><li>-f — path to output file of the trained model</li><li>-b 28 — we will use 28 bits for hashing, resulting in a $2^{28}$-sized feature space</li><li>fix random seed for reproducibility</li></ul><pre>%%time !vw --oaa 10 -d $PATH_TO_STACKOVERFLOW_DATA/stackoverflow_train.vw \ -f vw_model1_10mln.vw -b 28 --random_seed 17 --quiet</pre><pre># Output:</pre><pre>CPU times: user 559 ms, sys: 171 ms, total: 730 ms Wall time: 38.5 s</pre><pre><br>%%time !vw -t -i vw_model1_10mln.vw -d $PATH_TO_STACKOVERFLOW_DATA/stackoverflow_test.vw \ -p vw_test_pred.csv --random_seed 17 --quiet</pre><pre># Output:</pre><pre>CPU times: user 322 ms, sys: 97 ms, total: 420 ms Wall time: 22.8 s</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/790add7ab901b88cd621324bfb284dc4/href">https://medium.com/media/790add7ab901b88cd621324bfb284dc4/href</a></iframe><pre># Output:</pre><pre>0.91728604842865913</pre><p>The model has trained itself and made predictions in less than a minute (check it, these results are reported for a MacBook Pro, mid 2015, 2.2 GHz Intel Core i7, 16GB RAM). Its accuracy is almost 92%. All of this without a Hadoop cluster! :) Impressive, isn’t it?</p><h3>4. Assignment #8</h3><p>Full versions of assignments are announced each week in a new run of the course (October 1, 2018). Meanwhile, you can practice with a demo version: <a href="https://www.kaggle.com/kashnitsky/assignment-8-implementing-online-regressor">Kaggle Kernel</a>, <a href="https://mlcourse.ai/notebooks/blob/master/jupyter_english/assignments_demo/assignment08_implement_sgd_regressor.ipynb?flush_cache=true">nbviewer</a>.</p><h3>5. Useful resources</h3><ul><li>Official VW <a href="https://github.com/JohnLangford/vowpal_wabbit/wiki">documentation</a> on Github</li><li><a href="http://www.deeplearningbook.org/contents/numerical.html">“Numeric Computation” Chapter</a> of the <a href="http://www.deeplearningbook.org/">Deep Learning book</a></li><li><a href="https://www.amazon.com/Convex-Optimization-Stephen-Boyd/dp/0521833787">“Convex Optimization” by Stephen Boyd</a></li><li>“Command-line Tools can be 235x Faster than your Hadoop Cluster” <a href="https://aadrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html">post</a></li><li>Benchmarking various ML algorithms on Criteo 1TB dataset on <a href="https://github.com/rambler-digital-solutions/criteo-1tb-benchmark">GitHub</a></li><li>VW on <a href="http://fastml.com/blog/categories/vw/">FastML.com</a></li></ul><p><em>Author: </em><a href="https://yorko.github.io/"><em>Yury Kashnitsky</em></a><em>. Translated and edited by </em><a href="https://www.linkedin.com/in/sergeoreshkov/"><em>Serge Oreshkov</em></a><em>, and </em><a href="https://www.linkedin.com/in/yuanyuanpao/"><em>Yuanyuan Pao</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=60f750086237" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-8-vowpal-wabbit-fast-learning-with-gigabytes-of-data-60f750086237">Open Machine Learning Course. Topic 8. Vowpal Wabbit: Fast Learning with Gigabytes of Data</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 7. Unsupervised Learning: PCA and Clustering]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-7-unsupervised-learning-pca-and-clustering-db7879568417?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/db7879568417</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[education]]></category>
            <category><![CDATA[unsupervised-learning]]></category>
            <category><![CDATA[scikit-learn]]></category>
            <category><![CDATA[clustering]]></category>
            <dc:creator><![CDATA[Sergey Korolev]]></dc:creator>
            <pubDate>Mon, 26 Mar 2018 12:18:27 GMT</pubDate>
            <atom:updated>2018-09-19T09:10:07.102Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*9ZYZtx_Dl6i1bPCrAB8YaA.jpeg" /><figcaption>Popular visualization technique t-SNE for CIFAR-10 images using L2 distance. Image source: <a href="https://github.com/RutgersHan/blog/blob/master/source/_posts/CS231n-Image-Classification-Data-driven-Approach-k-Nearest-Neighbor-train-val-test-splits.md">CS231n course</a></figcaption></figure><p>Welcome to the seventh part of our Open Machine Learning Course!</p><p>In this lesson, we will work with unsupervised learning methods such as Principal Component Analysis (PCA) and clustering. You will learn why and how we can reduce the dimensionality of the original data and what the main approaches are for grouping similar data points.</p><h3>Article outline</h3><ol><li>Introduction</li><li>Principal Component Analysis</li></ol><ul><li>Intuition, theories, and application issues</li><li>Application examples</li></ul><p>3. Cluster analysis</p><ul><li>K-means</li><li>Affinity Propagation</li><li>Spectral clustering</li><li>Agglomerative clustering</li><li>Accuracy metrics</li></ul><p>4. Assignment #7</p><p>5. Useful links</p><h3>1. Introduction</h3><p>The main feature of unsupervised learning algorithms, when compared to classification and regression methods, is that input data are unlabeled (i.e. no labels or classes given) and that the algorithm learns the structure of the data without any assistance. This creates two main differences. First, it allows us to process large amounts of data because the data does not need to be manually labeled. Second, it is difficult to evaluate the quality of an unsupervised algorithm due to the absence of an explicit goodness metric as used in supervised learning.</p><p>One of the most common tasks in unsupervised learning is dimensionality reduction. On one hand, dimensionality reduction may help with data visualization (e.g. t-SNA method) while, on the other hand, it may help deal with the multicollinearity of your data and prepare the data for a supervised learning method (e.g. decision trees).</p><h3>2. Principal Component Analysis (PCA)</h3><h3>Intuition, theories, and application issues</h3><p>Principal Component Analysis is one of the easiest, most intuitive, and most frequently used methods for dimensionality reduction, projecting data onto its orthogonal feature subspace.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ai1Kqn4DxKc3lm3I.png" /></figure><p>More generally speaking, all observations can be considered as an ellipsoid in a subspace of an initial feature space, and the new basis set in this subspace is aligned with the ellipsoid axes. This assumption lets us remove highly correlated features since basis set vectors are orthogonal. In the general case, the resulting ellipsoid dimensionality matches the initial space dimensionality, but the assumption that our data lies in a subspace with a smaller dimension allows us to cut off the “excessive” space with the new projection (subspace). We accomplish this in a ‘greedy’ fashion, sequentially selecting each of the ellipsoid axes by identifying where the dispersion is maximal.</p><blockquote><em>“To deal with hyper-planes in a 14 dimensional space, visualize a 3D space and say ‘fourteen’ very loudly. Everyone does it.” — Geoffrey Hinton</em></blockquote><p>Let’s take a look at the mathematical formulation of this process:</p><p>In order to decrease the dimensionality of our data from n to k with k ≤ n, we sort our list of axes in order of decreasing dispersion and take the top-k of them.</p><p>We begin by computing the dispersion and the covariance of the initial features. This is usually done with the covariance matrix. According to the covariance definition, the covariance of two features is computed as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*545foTNYANSS8uYOCyYPsw.png" /></figure><p>where µ is the expected value of the i-th feature. It is worth noting that the covariance is symmetric, and the covariance of a vector with itself is equal to its dispersion.</p><p>Therefore the covariance matrix is symmetric with the dispersion of the corresponding features on the diagonal. Non-diagonal values are the covariances of the corresponding pair of features. In terms of matrices where X is the matrix of observations, the covariance matrix is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d9eix72-soDtISrW5KzD1Q.png" /></figure><p>Quick recap: matrices, as linear operators, have eigenvalues and eigenvectors. They are very convenient because they describe parts of our space that do not rotate and only stretch when we apply linear operators on them; eigenvectors remain in the same direction but are stretched by a corresponding eigenvalue. Formally, a matrix M with eigenvector w and eigenvalue λ satisfy this equation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I6g8B1etFk22t8sO4eKbLw.png" /></figure><p>The covariance matrix for a sample X can be written as a product of a transposed matrix X and X itself. According to the <a href="https://en.wikipedia.org/wiki/Rayleigh_quotient">Rayleigh quotient</a>, the maximum variation of our sample lies along the eigenvector of this matrix and is consistent with the maximum eigenvalue. Therefore, the principal components we aim to retain from the data are just the eigenvectors corresponding to the top-k largest eigenvalues of the matrix.</p><p>The next steps are easier to digest. We multiply the matrix of our data X by these components to get the projection of our data onto the orthogonal basis of the chosen components. If the number of components was smaller than the initial space dimensionality, remember that we will lose some information upon applying this transformation.</p><h3>Examples</h3><h4>Fisher’s iris dataset</h4><p>Let’s start by uploading all of the essential modules and try out the iris example from the scikit-learn documentation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/726e9698cde06492b62c448d6c5554f3/href">https://medium.com/media/726e9698cde06492b62c448d6c5554f3/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/849/1*LLVCG3YdynXytnW5-yml_g.png" /></figure><p>Now let’s see how PCA will improve the results of a simple model that is not able to correctly fit all of the training data:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ffdff8d794ee13a4c0473cb74e035c39/href">https://medium.com/media/ffdff8d794ee13a4c0473cb74e035c39/href</a></iframe><pre>Accuracy: 0.88889</pre><p>Let’s try this again, but, this time, let’s reduce the dimensionality to 2 dimensions:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/69c9db1d439bc62202c9775eec74172e/href">https://medium.com/media/69c9db1d439bc62202c9775eec74172e/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/751/1*97YNizp9y66HjlOTjUTVMQ.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/16767dd05db14691b4f4a34d10795fbe/href">https://medium.com/media/16767dd05db14691b4f4a34d10795fbe/href</a></iframe><pre>Accuracy: 0.91111</pre><p>The accuracy did not increase significantly in this case, but, with other datasets with a high number of dimensions, PCA can drastically improve the accuracy of decision trees and other ensemble methods.</p><p>Now let’s check out the percent of variance that can be explained by each of the selected components.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7a6fce578596334882f046b6416321eb/href">https://medium.com/media/7a6fce578596334882f046b6416321eb/href</a></iframe><pre>1 component: 92.46% of initial variance 0.362 x sepal length (cm) + -0.082 x sepal width (cm) + 0.857 x petal length (cm) + 0.359 x petal width (cm) <br>2 component: 5.3% of initial variance 0.657 x sepal length (cm) + 0.730 x sepal width (cm) + -0.176 x petal length (cm) + -0.075 x petal width (cm)</pre><h3>Handwritten numbers dataset</h3><p>Let’s look at the handwritten numbers dataset that we used before in the <a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-3-classification-decision-trees-and-k-nearest-neighbors-8613c6b6d2cd">3rd lesson</a>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/57af70cffb0bc0653138bc87e22dacbb/href">https://medium.com/media/57af70cffb0bc0653138bc87e22dacbb/href</a></iframe><p>Let’s start by visualizing our data. Fetch the first 10 numbers. The numbers are represented by 8 x 8 matrixes with the color intensity for each pixel. Every matrix is flattened into a vector of 64 numbers, so we get the feature version of the data.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/55548e56dd260f5c0785f331afb8f01c/href">https://medium.com/media/55548e56dd260f5c0785f331afb8f01c/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1rB4YYr9COBNuvlEfTcSMw.png" /></figure><p>Our data has 64 dimensions, but we are going to reduce it to only 2 and see that, even with just 2 dimensions, we can clearly see that digits separate into clusters.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c0e9547a0172a9f86a5e73b5745d3f82/href">https://medium.com/media/c0e9547a0172a9f86a5e73b5745d3f82/href</a></iframe><pre>Projecting 64-dimensional data to 2D</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LM87b7T7Ok6ferSzUuwPTg.png" /></figure><p>Indeed, with t-SNE, the picture looks better since PCA has a linear constraint while t-SNE does not. However, even with such a small dataset, the t-SNE algorithm takes significantly more time to complete than PCA.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4c83412973af2e00e0df6d3e1adc9874/href">https://medium.com/media/4c83412973af2e00e0df6d3e1adc9874/href</a></iframe><pre>CPU times: user 29.3 s, sys: 2.97 s, total: 32.3 s Wall time: 32.3 s</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0hQurqZd0heuS9TdRpbozw.png" /></figure><p>In practice, we would choose the number of principal components such that we can explain 90% of the initial data dispersion (via the explained_variance_ratio). Here, that means retaining 21 principal components; therefore, we reduce the dimensionality from 64 features to 21.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/472790a3601fd378eb924a80fd34824b/href">https://medium.com/media/472790a3601fd378eb924a80fd34824b/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8AEWO482TMsp5ybMz0R3tA.png" /></figure><h3>2. Clustering</h3><p>The main idea behind clustering is pretty straightforward. Basically, we say to ourselves, “I have these points here, and I can see that they organize into groups. It would be nice to describe these things more concretely, and, when a new point comes in, assign it to the correct group.” This general idea encourages exploration and opens up a variety of algorithms for clustering.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zgxSvaRl54FMeSao.png" /><figcaption>The examples of the outcomes from different algorithms from scikit-learn</figcaption></figure><p>The algorithms listed below do not cover all the clustering methods out there, but they are the most commonly used ones.</p><h3>K-means</h3><p>K-means algorithm is the most popular and yet simplest of all the clustering algorithms. Here is how it works:</p><ol><li>Select the number of clusters k that you think is the optimal number.</li><li>Initialize k points as “centroids” randomly within the space of our data.</li><li>Attribute each observation to its closest centroid.</li><li>Update the centroids to the center of all the attributed set of observations.</li><li>Repeat steps 3 and 4 a fixed number of times or until all of the centroids are stable (i.e. no longer change in step 4).</li></ol><p>This algorithm is easy to describe and visualize. Let’s take a look.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9044357063ccfd295c244d011719cda9/href">https://medium.com/media/9044357063ccfd295c244d011719cda9/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/623/1*PkyOs7WMxltZcV3tu5n_vg.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/523a18a167e4f9f21d1b6629e0149f94/href">https://medium.com/media/523a18a167e4f9f21d1b6629e0149f94/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f97e58586efcb009a4653cd1d073867f/href">https://medium.com/media/f97e58586efcb009a4653cd1d073867f/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/958/1*IvE1IbWyE4OkdM_qP2eDgw.png" /></figure><p>Here, we used Euclidean distance, but the algorithm will converge with any other metric. You can not only vary the number of steps or the convergence criteria but also the distance measure between the points and cluster centroids.</p><p>Another “feature” of this algorithm is its sensitivity to the initial positions of the cluster centroids. You can run the algorithm several times and then average all the centroid results.</p><h4>Choosing the number of clusters for K-means</h4><p>In contrast to the supervised learning tasks such as classification and regression, clustering requires more effort to choose the optimization criterion. Usually, when working with k-means, we optimize the sum of squared distances between the observations and their centroids.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t00n9lPVT_otEyRyOm_1Ig.png" /></figure><p>where C — is a set of clusters with power K, µ is a centroid of a cluster.</p><p>This definition seems reasonable — we want our observations to be as close to their centroids as possible. But, there is a problem — the optimum is reached when the number of centroids is equal to the number of observations, so you would end up with every single observation as its own separate cluster.</p><p>In order to avoid that case, we should choose a number of clusters after which a function J(C) is decreasing less rapidly. More formally,</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pEinmyQl2crsK296fl68-Q.png" /></figure><p>Let’s look at an example.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/086da6557935a017d72b4dda17118320/href">https://medium.com/media/086da6557935a017d72b4dda17118320/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/783/1*EBU5SH310PDe2olr5aFVyQ.png" /></figure><p>We see that J(C) decreases significantly until the number of clusters is 3 and then does not change as much anymore. This means that the optimal number of clusters is 3.</p><h4>Issues</h4><p>Inherently, K-means is NP-hard. For d dimensions, k clusters, and n observations, we will find a solution in O(n^(dk+1)) in time. There are some heuristics to deal with this; an example is MiniBatch K-means, which takes portions (batches) of data instead of fitting the whole dataset and then moves centroids by taking the average of the previous steps. Compare the implementation of K-means and MiniBatch K-means in the <a href="http://scikit-learn.org/stable/auto_examples/cluster/plot_mini_batch_kmeans.html">sckit-learn documentation</a>.</p><p>The <a href="http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html">implemetation</a> of the algorithm using scikit-learn has its benefits such as the possibility to state the number of initializations with the n_init function parameter, which enables us to identify more robust centroids. Moreover, these runs can be done in parallel to decrease the computation time.</p><h3>Affinity Propagation</h3><p>Affinity propagation is another example of a clustering algorithm. As opposed to K-means, this approach does not require us to set the number of clusters beforehand. The main idea here is that we would like to cluster our data based on the similarity of the observations (or how they “correspond” to each other).</p><p>Let’s define a similarity metric such that s(x, y) &gt; s(x, z) if an observation x is more similar to observation y and less similar to observation z. A simple example of such a similarity metric is a negative square of distance s(x, y) = -||x — y||².</p><p>Now, let’s describe “correspondence” by making two zero matrices. One of them, r, determines how well the k-th observation is as a “role model” for the i-th observation with respect to all other possible “role models”. Another matrix, a determines how appropriate it would be for i-th observation to take the k-th observation as a “role model”. This may sound confusing, but it becomes more understandable with some hands-on practice.</p><p>The matrices are updated sequentially with the following rules:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r8rCyn3oZq_T7w32qdfrBw.png" /></figure><h3>Spectral clustering</h3><p>Spectral clustering combines some of the approaches described above to create a stronger clustering method.</p><p>First of all, this algorithm requires us to define the similarity matrix for observations called the adjacency matrix. This can be done in a similar fashion as in the Affinity Propagation algorithm, so that matrix A hosts negative square of the distances between the corresponding points. This matrix describes a full graph with the observations as vertices and the estimated similarity value between a pair of observations as edge weights for that pair of vertices. For the metric defined above and two-dimensional observations, this is pretty intuitive — two observations are similar if the edge between them is shorter. We’d like to split up the graph into two subgraphs in such a way that each observation in each subgraph would be similar to another observation in that subgraph. Formally, this is a Normalized cuts problem; for more details, we recommend reading <a href="http://people.eecs.berkeley.edu/%7Emalik/papers/SM-ncut.pdf">this paper</a>.</p><h3>Agglomerative clustering</h3><p>The following algorithm is the simplest and easiest to understand among all the the clustering algorithms without a fixed number of clusters.</p><p>The algorithm is fairly simple:</p><ol><li>We start by assigning each observation to its own cluster</li><li>Then sort the pairwise distances between the centers of clusters in descending order</li><li>Take the nearest two neigbor clusters and merge them together, and recompute the centers</li><li>Repeat steps 2 and 3 until all the data is merged into one cluster</li></ol><p>The process of searching for the nearest cluster can be conducted with different methods of bounding the observations:</p><ol><li>Single linkage — distance between centroids is the minimum distance between all the pairs of points where one is from the first cluster, and the second is from the other.</li><li>Complete linkage — distance between centroids is the maximum distance between all the pairs of points where one is from the first cluster, and the second is from the other.</li><li>Average linkage — distance between centroids is the adjusted sum of all of the distances between all the pairs of points where one is from the first cluster, and the second is from the other.</li><li>Centroid linkage — distance between centroids is the distance between their geometric centers of clusters.</li></ol><p>The 3rd one is the most effective in computation time since it does not require recomputing the distances every time the clusters are merged.</p><p>The results can be visualized as a beautiful cluster tree (dendogram) to help recognize the moment the algorithm should be stopped to get optimal results. There are plenty of Python tools to build these dendograms for agglomerative clustering.</p><p>Let’s consider an example with the clusters we got from K-means:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/89a3085d6657ca755058870ace4d909f/href">https://medium.com/media/89a3085d6657ca755058870ace4d909f/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2KpWby5gD5xeuu7jYax5tg.png" /></figure><h3>Accuracy metrics</h3><p>As opposed to classfication, it is difficult to assess the quality of results from clustering. Here, a metric cannot depend on the labels but only on the goodness of split. Secondly, we do not usually have true labels of the observations when we use clustering.</p><p>There are <em>internal</em> and <em>external</em> goodness metrics. External metrics use the information about the known true split while internal metrics do not use any external information and assess the goodness of clusters based only on the initial data. The optimal number of clusters is usually defined with respect to some internal metrics.</p><p>All the metrics described below are implemented in sklearn.metrics.</p><p><strong>Adjusted Rand Index (ARI)</strong></p><p>Here, we assume that the true labels of objects are known. This metric does not depend on the labels’ values but on the data cluster split. Let N be the number of observations in a sample. Let a to be the number of observation pairs with the same labels and located in the same cluster, and let b to be the number of observations with different labels and located in different clusters. The Rand Index can be calculated using the following formula: RI = 2(a + b)/n(n — 1) In other words, it evaluates a share of observations for which these splits (initial and clustering result) are consistent. The Rand Index (RI) evaluates the similarity of the two splits of the same sample. In order for this index to be close to zero for any clustering outcomes with any n and number of clusters, it is essential to scale it, hence the Adjusted Rand Index: ARI = (RI — E(RI))/(max(RI) — E(RI)).</p><p>This metric is symmetric and does not depend in the label permutation. Therefore, this index is a measure of distances between different sample splits. ARI takes on values in the [-1, 1] range. Negative values indicate the independence of splits, and positive values indicate that these splits are consistent (they match ARI = 1).</p><p><strong>Adjusted Mutual Information (AMI)</strong></p><p>This metric is similar to ARI. It is also symmetric and does not depend on the labels’ values and permutation. It is defined by the [entropy](<a href="https://en.wikipedia.org/wiki/Entropy_(information_theory)">https://en.wikipedia.org/wiki/Entropy_(information_theory)</a> function and interprets a sample split as a discrete distribution (likelihood of assigning to a cluster is equal to the percent of objects in it). The MI index is defined as the <a href="https://en.wikipedia.org/wiki/Mutual_information">mutual information</a> for two distributions, corresponding to the sample split into clusters. Intuitively, the mutual information measures the share of information common for both clustering splits i.e. how information about one of them decreases the uncertainty of the other one.</p><p>Similarly to the ARI, the AMI is defined. This allows us to get rid of the MI index’s increase with the number of clusters. The AMI lies in the [0, 1] range. Values close to zero mean the splits are independent, and those close to 1 mean they are similar (with complete match at AMI = 1).</p><p><strong>Homogeneity, completeness, V-measure</strong></p><p>Formally, these metrics are also defined based on the entropy function and the conditional entropy function, interpreting the sample splits as discrete distributions:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fR6e2PiJmMu1_auXNOSB3w.png" /></figure><p>where K is a clustering result and C is the initial split. Therefore, h evaluates whether each cluster is composed of same class objects, and c measures how well the same class objects fit the clusters. These metrics are not symmetric. Both lie in the [0, 1] range, and values closer to 1 indicate more accurate clustering results. These metrics’ values are not scaled as the ARI or AMI metrics are and thus depend on the number of clusters. A random clustering result will not have metrics’ values closer to zero when the number of clusters is big enough and the number of objects is small. In such a case, it would be more reasonable to use ARI. However, with a large number of observations (more than 100) and the number of clusters less than 10, this issue is less critical and can be ignored.</p><p>V-measure is a combination of h, and c and is their harmonic mean: v = (2hc)/(h + c). It is symmetric and measures how consistent two clustering results are.</p><p><strong>Silhouette</strong></p><p>In contrast to the metrics described above, this coefficient does not imply the knowledge about the true labels of the objects. It lets us estimate the quality of the clustering using only the initial, unlabeled sample and the clustering result. To start with, for each observation, the silhouette coefficient is computed. Let a be the mean of the distance between an object and other objects within one cluster and b be the mean distance from an object to an object from the nearest cluster (different from the one the object belongs to). Then the silhouette measure for this object is s = (b — a)/max(a, b).</p><p>The silhouette of a sample is a mean value of silhouette values from this sample. Therefore, the silhouette distance shows to which extent the distance between the objects of the same class differ from the mean distance between the objects from different clusters. This coefficient takes values in the [-1, 1] range. Values close to -1 correspond to bad clustering results while values closer to 1 correspond to dense, well-defined clusters. Therefore, the higher the silhouette value is, the better the results from clustering.</p><p>With the help of silhouette, we can identify the optimal number of clusters k (if we don’t know it already from the data) by taking the number of clusters that maximizes the silhouette coefficient.</p><p>To conclude, let’s take a look at how these metrics perform with the MNIST handwritten numbers dataset:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0035cf36d15ddd9b6eb86f64343b18fb/href">https://medium.com/media/0035cf36d15ddd9b6eb86f64343b18fb/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XG2eCA93Py2YbAQ9fBkGWw.png" /></figure><h3>4. Assignment #7</h3><p>Full versions of assignments are announced each week in a new run of the course (October 1, 2018). Meanwhile, you can practice with a demo version: <a href="https://www.kaggle.com/kashnitsky/assignment-7-unupervised-learning">Kaggle Kernel</a>, <a href="https://mlcourse.ai/notebooks/blob/master/jupyter_english/assignments_demo/assignment07_unsupervised_learning.ipynb?flush_cache=true">nbviewer</a>.</p><h3>5. Useful links</h3><ul><li>Overview of clustering methods in the <a href="http://scikit-learn.org/stable/modules/clustering.html">scikit-learn doc</a>.</li><li><a href="http://stats.stackexchange.com/questions/2691/making-sense-of-principal-component-analysis-eigenvectors-eigenvalues">Q&amp;A</a> for PCA with examples</li><li><a href="https://github.com/diefimov/MTH594_MachineLearning/blob/master/ipython/Lecture10.ipynb">Notebook</a> on k-means and the EM-algorithm</li></ul><p><em>Author: </em><a href="https://www.linkedin.com/in/sokorolev/"><em>Sergey Korolev</em></a><em>, Software Engineer at Snap Inc. Translated and edited by </em><a href="https://www.linkedin.com/in/anna-golovchenko-b0ba5a112/"><em>Anna Golovchenko</em></a><em>, </em><a href="https://www.linkedin.com/in/festline/"><em>Yury Kashnitskiy</em></a><em>, and </em><a href="https://www.linkedin.com/in/yuanyuanpao/"><em>Yuanyuan Pao</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=db7879568417" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-7-unsupervised-learning-pca-and-clustering-db7879568417">Open Machine Learning Course. Topic 7. Unsupervised Learning: PCA and Clustering</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 6. Feature Engineering and Feature Selection]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-6-feature-engineering-and-feature-selection-8b94f870706a?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/8b94f870706a</guid>
            <category><![CDATA[feature-engineering]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[feature-extraction]]></category>
            <category><![CDATA[feature-selection]]></category>
            <dc:creator><![CDATA[Arseny Kravchenko]]></dc:creator>
            <pubDate>Mon, 12 Mar 2018 11:01:01 GMT</pubDate>
            <atom:updated>2018-09-19T09:07:48.338Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/454/0*NZRVMpxF8oSBwumh." /></figure><p>In this course, we have already seen several key machine learning algorithms. However, before moving on to the more fancy ones, we’d like to take a small detour and talk about data preparation. The well-known concept of “garbage in — garbage out” applies 100% to any task in machine learning. Any experienced professional can recall numerous times when a simple model trained on high-quality data was proven to be better than a complicated multi-model ensemble built on data that wasn’t clean.</p><p>To start, I wanted to review three similar but different tasks:</p><ul><li><strong>feature extraction</strong> and <strong>feature engineering</strong>: transformation of raw data into features suitable for modeling;</li><li><strong>feature transformation</strong>: transformation of data to improve the accuracy of the algorithm;</li><li><strong>feature selection</strong>: removing unnecessary features.</li></ul><p>This article will contain almost no math, but there will be a fair amount of code. (<a href="https://github.com/Yorko/mlcourse_open/blob/master/jupyter_english/topic06_features/topic6_feature_engineering_feature_selection.ipynb">Jupyter</a> version). Some examples will use the dataset from Renthop company, which is used in the <a href="https://www.kaggle.com/c/two-sigma-connect-rental-listing-inquiries">Two Sigma Connect: Rental Listing Inquiries Kaggle competition</a>. In this task, you need to predict the popularity of a new rental listing, i.e. classify the listing into three classes: [&#39;low&#39;, &#39;medium&#39; , &#39;high&#39;]. To evaluate the solutions, we will use the log loss metric (the smaller, the better). Those who do not have a Kaggle account, will have to register; you will also need to accept the rules of the competition in order to download the data.</p><pre><strong>import</strong> <strong>json</strong><br><strong>import</strong> <strong>pandas</strong> <strong>as</strong> <strong>pd</strong><br><br><em># Let&#39;s load the dataset from Renthop right away</em><br><strong>with</strong> open(&#39;train.json&#39;, &#39;r&#39;) <strong>as</strong> raw_data:<br>    data = json.load(raw_data)<br>    df = pd.DataFrame(data)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/593/1*9hnjnSWUMMwg7AkfceIeTA.png" /></figure><h3>Article outline</h3><p>1. Feature Extraction<br>1.1. Texts<br>1.2. Images<br>1.3. Geospatial data<br>1.4. Date and time<br>1.5. Time series, web, etc.</p><p>2. Feature transformations<br>2.1. Normalization and changing distribution<br>2.2. Interactions<br>2.3. Filling in the missing values</p><p>3. Feature selection<br>3.1. Statistical approaches<br>3.2. Selection by modeling<br>3.3. Grid search</p><h3>1. Feature Extraction</h3><p>In practice, data rarely comes in the form of ready-to-use matrices. That&#39;s why every task begins with feature extraction. Sometimes, it can be enough to read the csv file and convert it into numpy.array, but this is a rare exception. Let&#39;s look at some of the popular types of data from which features can be extracted.</p><h3>1.1. Texts</h3><p>Text is a type of data that can come in different formats; there are so many text processing methods that cannot fit in a single article. Nevertheless, we will review the most popular ones.</p><p>Before working with text, one must tokenize it. Tokenization implies splitting the text into units (hence, tokens). Most simply, tokens are just the words. But splitting by word can lose some of the meaning -- &quot;Santa Barbara&quot; is one token, not two, but &quot;rock&#39;n&#39;roll&quot; should not be split into two tokens. There are ready-to-use tokenizers that take into account peculiarities of the language, but they make mistakes as well, especially when you work with specific sources of text (newspapers, slang, misspellings, typos).</p><p>After tokenization, you will normalize the data. For text, this is about stemming and/or lemmatization; these are similar processes used to process different forms of a word. One can read about the difference between them <a href="http://nlp.stanford.edu/IR-book/html/htmledition/stemming-and-lemmatization-1.html">here</a>.</p><p>So, now that we have turned the document into a sequence of words, we can represent it with vectors. The easiest approach is called Bag of Words: we create a vector with the length of the dictionary, compute the number of occurrences of each word in the text, and place that number of occurrences in the appropriate position in the vector. The process described looks simpler in code:</p><pre><strong>from</strong> <strong>functools</strong> <strong>import</strong> reduce <br><strong>import</strong> <strong>numpy</strong> <strong>as</strong> <strong>np</strong><br><br>texts = [[&#39;i&#39;, &#39;have&#39;, &#39;a&#39;, &#39;cat&#39;], <br>        [&#39;he&#39;, &#39;have&#39;, &#39;a&#39;, &#39;dog&#39;], <br>        [&#39;he&#39;, &#39;and&#39;, &#39;i&#39;, &#39;have&#39;, &#39;a&#39;, &#39;cat&#39;, &#39;and&#39;, &#39;a&#39;, &#39;dog&#39;]]<br><br>dictionary = list(enumerate(set(list(reduce(<strong>lambda</strong> x, y: x + y, texts)))))<br><br><strong>def</strong> vectorize(text): <br>    vector = np.zeros(len(dictionary)) <br>    <strong>for</strong> i, word <strong>in</strong> dictionary: <br>        num = 0 <br>        <strong>for</strong> w <strong>in</strong> text: <br>            <strong>if</strong> w == word: <br>                num += 1 <br>        <strong>if</strong> num: <br>            vector[i] = num <br>    <strong>return</strong> vector<br><br><strong>for</strong> t <strong>in</strong> texts: <br>    <strong>print</strong>(vectorize(t))</pre><p>Expected result:</p><pre>[0. 1. 0. 1. 1. 0. 1.]<br>[0. 1. 1. 0. 1. 1. 0.]<br>[2. 1. 1. 1. 2. 1. 1.]</pre><p>Here is an illustration of the process:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/779/1*XAUdLzs_GQ4YWjA3dk5Geg.png" /></figure><p>This is an extremely naive implementation. In practice, you need to consider stop words, the maximum length of the dictionary, more efficient data structures (usually text data is converted to a sparse vector), etc.</p><p>When using algorithms like Bag of Words, we lose the order of the words in the text, which means that the texts &quot;i have no cows&quot; and &quot;no, i have cows&quot; will appear identical after vectorization when, in fact, they have the opposite meaning. To avoid this problem, we can revisit our tokenization step and use N-grams (the <em>sequence</em> of N consecutive tokens) instead.</p><pre>In : <strong>from</strong> <strong>sklearn.feature_extraction.text</strong> <strong>import</strong> CountVectorizer<br><br>In : vect = CountVectorizer(ngram_range=(1,1))<br><br>In : vect.fit_transform([&#39;no i have cows&#39;, &#39;i have no cows&#39;]).toarray() <br>Out: <br>array([[1, 1, 1],<br>      [1, 1, 1]], dtype=int64)<br><br>In : vect.vocabulary_ <br>Out: {&#39;cows&#39;: 0, &#39;have&#39;: 1, &#39;no&#39;: 2}<br><br>In : vect = CountVectorizer(ngram_range=(1,2))<br><br>In : vect.fit_transform([&#39;no i have cows&#39;, &#39;i have no cows&#39;]).toarray() <br>Out: <br>array([[1, 1, 1, 0, 1, 0, 1], <br>      [1, 1, 0, 1, 1, 1, 0]], dtype=int64)<br><br>In : vect.vocabulary_ <br>Out: {&#39;cows&#39;: 0, <br>      &#39;have&#39;: 1,<br>      &#39;have cows&#39;: 2,<br>      &#39;have no&#39;: 3,<br>      &#39;no&#39;: 4,<br>      &#39;no cows&#39;: 5,<br>      &#39;no have&#39;: 6}</pre><p>Also note that one does not have to use only words. In some cases, it is possible to generate N-grams of characters. This approach would be able to account for similarity of related words or handle typos.</p><pre><strong>from</strong> <strong>scipy.spatial.distance</strong> <strong>import</strong> euclidean<br><strong>from</strong> <strong>sklearn.feature_extraction.text</strong> <strong>import</strong> CountVectorizer<br><br>vect = CountVectorizer(ngram_range=(3,3), analyzer=&#39;char_wb&#39;)<br><br>n1, n2, n3, n4 = vect.fit_transform([&#39;andersen&#39;, &#39;petersen&#39;, &#39;petrov&#39;, &#39;smith&#39;]).toarray()<br><br>euclidean(n1, n2), euclidean(n2, n3), euclidean(n3, n4)<br># (2.8284271247461903, 3.1622776601683795, 3.3166247903554)</pre><p>Adding onto the Bag of Words idea: words that are rarely found in the corpus (in all the documents of this dataset) but are present in this particular document might be more important. Then it makes sense to increase the weight of more domain-specific words to separate them out from common words. This approach is called TF-IDF (term frequency-inverse document frequency), which cannot be written in a few lines, so you should look into the details in references such as <a href="https://en.wikipedia.org/wiki/Tf%E2%80%93idf">this wiki</a>. The default option is as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/362/1*0GBn_uP9KZNHLkvUXu4hgA.png" /></figure><p>Analogs of Bag of Words can be found outside of text problems e.g. bag of sites in the <a href="https://inclass.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking">Catch Me If You Can competition</a>, <a href="https://www.kaggle.com/xiaoml/talkingdata-mobile-user-demographics/bag-of-app-id-python-2-27392">bag of apps</a>, <a href="http://www.interdigital.com/download/58540a46e3b9659c9f000372">bag of events</a>, etc.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/779/1*XAUdLzs_GQ4YWjA3dk5Geg.png" /></figure><p>Using these algorithms, it is possible to obtain a working solution for a simple problem, which can serve as a baseline. However, for those who do not like the classics, there are new approaches. The most popular method in the new wave is Word2Vec, but there are a few alternatives as well (GloVe, Fasttext, etc.).</p><p>Word2Vec is a special case of the word embedding algorithms. Using Word2Vec and similar models, we can not only vectorize words in a high-dimensional space (typically a few hundred dimensions) but also compare their semantic similarity. This is a classic example of operations that can be performed on vectorized concepts: king - man + woman = queen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/336/1*K5X4N-MJKt8FGFtrTHwidg.gif" /></figure><p>It is worth noting that this model does not comprehend the meaning of the words but simply tries to position the vectors such that words used in common context are close to each other. If this is not taken into account, a lot of fun examples will come up.</p><p>Such models need to be trained on very large datasets in order for the vector coordinates to capture the semantics. A pretrained model for your own tasks can be downloaded <a href="https://github.com/3Top/word2vec-api#where-to-get-a-pretrained-models">here</a>.</p><p>Similar methods are applied in other areas such as bioinformatics. An unexpected application is <a href="https://jaan.io/food2vec-augmented-cooking-machine-intelligence/">food2vec</a>. You can probably think of a few other fresh ideas; the concept is universal enough.</p><h3>1.2. Images</h3><p>Working with images is easier and harder at the same time. It is easier because it is possible to just use one of the popular pretrained networks without much thinking but harder because, if you need to dig into the details, you may end up going really deep. Let&#39;s start from the beginning.</p><p>In a time when GPUs were weaker and the &quot;renaissance of neural networks&quot; had not happened yet, feature generation from images was its own complex field. One had to work at a low level, determining corners, borders of regions, color distributions statistics, and so on. Experienced specialists in computer vision could draw a lot of parallels between older approaches and neural networks; in particular, convolutional layers in today&#39;s networks are similar to <a href="https://habrahabr.ru/post/208092/">Haar cascades</a>. If you are interested in reading more, here are a couple of links to some interesting libraries: <a href="http://scikit-image.org/docs/stable/api/skimage.feature.html">skimage</a> and <a href="http://simplecv.readthedocs.io/en/latest/SimpleCV.Features.html">SimpleCV</a>.</p><p>Often for problems associated with images, a convolutional neural network is used. You do not have to come up with the architecture and train a network from scratch. Instead, download a pretrained state-of-the-art network with the weights from public sources. Data scientists often do so-called fine-tuning to adapt these networks to their needs by &quot;detaching&quot; the last fully connected layers of the network, adding new layers chosen for a specific task, and then training the network on new data. If your task is to just vectorize the image (for example, to use some non-network classifier), you only need to remove the last layers and use the output from the previous layers:</p><pre><strong>from</strong> <strong>keras.applications.resnet50</strong> <strong>import</strong> ResNet50, preprocess_input<br><strong>from</strong> <strong>keras.preprocessing</strong> <strong>import</strong> image <br><strong>from</strong> <strong>scipy.misc</strong> <strong>import</strong> face <br><strong>import</strong> <strong>numpy</strong> <strong>as</strong> <strong>np</strong><br><br>resnet_settings = {&#39;include_top&#39;: False, &#39;weights&#39;: &#39;imagenet&#39;}<br>resnet = ResNet50(**resnet_settings)<br><br>img = image.array_to_img(face())<br><br><em># What a cute raccoon!</em><br><br>img = img.resize((224, 224))<br><br><em># In real life, you may need to pay more attention to resizing</em><br><br>x = image.img_to_array(img) <br>x = np.expand_dims(x, axis=0)<br>x = preprocess_input(x)<br><br><em># Need an extra dimension because model is designed to work with an array of images - i.e. tensor shaped (batch_size, width, height, n_channels)</em><br><br>features = resnet.predict(x)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Iw_cKFwLkTVO2SPrOZU2rQ.png" /></figure><p><em>Here&#39;s a classifier trained on one dataset and adapted for a different one by &quot;detaching&quot; the last layer and adding a new one instead.</em></p><p>Nevertheless, we should not focus too much on neural network techniques. Features generated by hand are still very useful: for example, for predicting the popularity of a rental listing, we can assume that bright apartments attract more attention and create a feature such as &quot;the average value of the pixel&quot;. You can find some inspiring examples in the documentation of <a href="http://pillow.readthedocs.io/en/3.1.x/reference/ImageStat.html">relevant libraries</a>.</p><p>If there is text on the image, you can read it without unraveling a complicated neural network. For example, check out <a href="https://github.com/madmaze/pytesseract">pytesseract</a>.</p><pre>In : <strong>import</strong> <strong>pytesseract</strong><br><br>In : <strong>from</strong> <strong>PIL</strong> <strong>import</strong> Image<br><br>In : <strong>import</strong> <strong>requests</strong><br><br>In : <strong>from</strong> <strong>io</strong> <strong>import</strong> BytesIO<br><br>In : img = &#39;http://ohscurrent.org/wp-content/uploads/2015/09/domus-01-google.jpg&#39;<br><br><em>##### Just a random picture from search</em><br><br>In : img = requests.get(img) <br>...: img = Image.open(BytesIO(img.content)) <br>...: text = pytesseract.image_to_string(img) <br>...:<br><br>In : text <br>Out: &#39;Google&#39;</pre><p>One must understand that pytesseract is not a solution for everything.</p><pre><em>##### This time we take a picture from Renthop</em><br><br>In : img = requests.get(&#39;https://photos.renthop.com/2/8393298_6acaf11f030217d05f3a5604b9a2f70f.jpg&#39;) <br>...: img = Image.open(BytesIO(img.content)) <br>...: pytesseract.image_to_string(img) <br>...:<br><br>Out: &#39;Cunveztible to 4}»&#39;</pre><p>Another case where neural networks cannot help is extracting features from meta-information. For images, EXIF stores many useful meta-information: manufacturer and camera model, resolution, use of the flash, geographic coordinates of shooting, software used to process image and more.</p><h3>1.3. Geospatial data</h3><p>Geographic data is not so often found in problems, but it is still useful to master the basic techniques for working with it, especially since there are quite a number of ready-to-use solutions in this field.</p><p>Geospatial data is often presented in the form of addresses or coordinates of (Latitude, Longitude). Depending on the task, you may need two mutually-inverse operations: geocoding (recovering a point from an address) and reverse geocoding (recovering an address from a point). Both operations are accessible in practice via external APIs from Google Maps or OpenStreetMap. Different geocoders have their own characteristics, and the quality varies from region to region. Fortunately, there are universal libraries like <a href="https://github.com/geopy/geopy">geopy</a> that act as wrappers for these external services.</p><p>If you have a lot of data, you will quickly reach the limits of external API. Besides, it is not always the fastest to receive information via HTTP. Therefore, it is necessary to consider using a local version of OpenStreetMap.</p><p>If you have a small amount of data, enough time, and no desire to extract fancy features, you can use reverse_geocoder in lieu of OpenStreetMap:</p><pre>In : <strong>import</strong> <strong>reverse_geocoder</strong> <strong>as</strong> <strong>revgc</strong><br><br>In : revgc.search((df.latitude, df.longitude)) <br>Loading formatted geocoded file... <br>Out: [OrderedDict([(&#39;lat&#39;, &#39;40.74482&#39;), <br>                   (&#39;lon&#39;, &#39;-73.94875&#39;), <br>                   (&#39;name&#39;, &#39;Long Island City&#39;), <br>                   (&#39;admin1&#39;, &#39;New York&#39;), <br>                   (&#39;admin2&#39;, &#39;Queens County&#39;), <br>                   (&#39;cc&#39;, &#39;US&#39;)])]</pre><p>When working with geoсoding, we must not forget that addresses may contain typos, which makes the data cleaning step necessary. Coordinates contain fewer misprints, but its position can be incorrect due to GPS noise or bad accuracy in places like tunnels, downtown areas, etc. If the data source is a mobile device, the geolocation may not be determined by GPS but by WiFi networks in the area, which leads to holes in space and teleportation. While traveling along in Manhattan, there can suddenly be a WiFi location from Chicago.</p><blockquote>WiFi location tracking is based on the combination of SSID and MAC-addresses, which may correspond to different points e.g. federal provider standardizes the firmware of routers up to MAC-address and places them in different cities. Even a company&#39;s move to another office with its routers can cause issues.</blockquote><p>The point is usually located among infrastructure. Here, you can really unleash your imagination and invent features based on your life experience and domain knowledge: the proximity of a point to the subway, the number of stories in the building, the distance to the nearest store, the number of ATMs around, etc. For any task, you can easily come up with dozens of features and extract them from various external sources. For problems outside an urban environment, you may consider features from more specific sources e.g. the height above sea level.</p><p>If two or more points are interconnected, it may be worthwhile to extract features from the route between them. In that case, distances (great circle distance and road distance calculated by the routing graph), number of turns with the ratio of left to right turns, number of traffic lights, junctions, and bridges will be useful. In one of my own tasks, I generated a feature called &quot;the complexity of the road&quot;, which computed the graph-calculated distance divided by the GCD.</p><h3>1.4. Date and time</h3><p>You would think that date and time are standardized because of their prevalence, but, nevertheless, some pitfalls remain.</p><p>Let&#39;s start with the day of the week, which are easy to turn into 7 dummy variables using one-hot encoding. In addition, we will also create a separate binary feature for the weekend called is_weekend.</p><pre>df[&#39;dow&#39;] = df[&#39;created&#39;].apply(<strong>lambda</strong> x: x.date().weekday())<br>df[&#39;is_weekend&#39;] = df[&#39;created&#39;].apply(<strong>lambda</strong> x: 1 <strong>if</strong> x.date().weekday() <strong>in</strong> (5, 6) <strong>else</strong> 0)</pre><p>Some tasks may require additional calendar features. For example, cash withdrawals can be linked to a pay day; the purchase of a metro card, to the beginning of the month. In general, when working with time series data, it is a good idea to have a calendar with public holidays, abnormal weather conditions, and other important events.</p><blockquote>Q: What do Chinese New Year, the New York marathon, and the Trump inauguration have in common?</blockquote><blockquote>A: They all need to be put on the calendar of potential anomalies.</blockquote><p>Dealing with hour (minute, day of the month ...) is not as simple as it seems. If you use the hour as a real variable, we slightly contradict the nature of data: 0&lt;23 while 0:00:00 02.01&gt; 01.01 23:00:00. For some problems, this can be critical. At the same time, if you encode them as categorical variables, you&#39;ll breed a large numbers of features and lose information about proximity -- the difference between 22 and 23 will be the same as the difference between 22 and 7.</p><p>There also exist some more esoteric approaches to such data like projecting the time onto a circle and using the two coordinates.</p><pre><strong>def</strong> make_harmonic_features(value, period=24):<br>    value *= 2 * np.pi / period <br>    <strong>return</strong> np.cos(value), np.sin(value)</pre><p>This transformation preserves the distance between points, which is important for algorithms that estimate distance (kNN, SVM, k-means ...)</p><pre>In : <strong>from</strong> <strong>scipy.spatial</strong> <strong>import</strong> distance<br><br>In : euclidean(make_harmonic_features(23), make_harmonic_features(1)) <br>Out: 0.5176380902050424<br><br>In : euclidean(make_harmonic_features(9), make_harmonic_features(11)) <br>Out: 0.5176380902050414<br><br>In : euclidean(make_harmonic_features(9), make_harmonic_features(21)) <br>Out: 2.0</pre><p>However, the difference between such coding methods is down to the third decimal place in the metric.</p><h3>1.5. Time series, web, etc.</h3><p>Regarding time series — we will not go into too much detail here (mostly due to my personal lack of experience), but I will point you to <a href="https://github.com/blue-yonder/tsfresh">a useful library that automatically generates features for time series</a>.</p><p>If you are working with web data, then you usually have information about the user&#39;s User Agent. It is a wealth of information. First, one needs to extract the operating system from it. Secondly, make a feature is_mobile. Third, look at the browser.</p><pre>In : ua = &#39;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/56.0.2924.76 Chrome/ <br>...: 56.0.2924.76 Safari/537.36&#39;<br><br>In : <strong>import</strong> <strong>user_agents</strong><br><br>In : ua = user_agents.parse(ua)<br><br>In : ua.is_bot <br>Out: False<br><br>In : ua.is_mobile <br>Out: False<br><br>In : ua.is_pc <br>Out: True<br><br>In : ua.os.family <br>Out: &#39;Ubuntu&#39;<br><br>In : ua.os.version <br>Out: ()<br><br>In : ua.browser.family <br>Out: &#39;Chromium&#39;<br><br>In : ua.os.version <br>Out: ()<br><br>In : ua.browser.version <br>Out: (56, 0, 2924)</pre><blockquote>As in other domains, you can come up with your own features based on intuition about the nature of the data. At the time of this writing, Chromium 56 was new, but, after some time, only users who haven&#39;t rebooted their browser for a long time will have this version. In this case, why not introduce a feature called &quot;lag behind the latest version of the browser&quot;?</blockquote><p>In addition to the operating system and browser, you can look at the referrer (not always available), <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language">http_accept_language</a>, and other meta information.</p><p>The next useful piece of information is the IP-address, from which you can extract the country and possibly the city, provider, and connection type (mobile/stationary). You need to understand that there is a variety of proxy and outdated databases, so this feature can contain noise. Network administration gurus may try to extract even fancier features like suggestions for <a href="https://habrahabr.ru/post/216295/">using VPN</a>. By the way, the data from the IP-address is well combined with http_accept_language: if the user is sitting at the Chilean proxies and browser locale is ru_RU, something is unclean and worth a look in the corresponding column in the table (is_traveler_or_proxy_user).</p><p>Any given area has so many specifics that it is too much for an individual to absorb completely. Therefore, I invite everyone to share their experiences and discuss feature extraction and generation in the comments section.</p><h3>2. Feature transformations</h3><h3>2.1. Normalization and changing distribution</h3><p>Monotonic feature transformation is critical for some algorithms and has no effect on others. This is one of the reasons for the increased popularity of decision trees and all its derivative algorithms (random forest, gradient boosting). Not everyone can or want to tinker with transformations, and these algorithms are robust to unusual distributions.</p><p>There are also purely engineering reasons: np.log is a way of dealing with large numbers that do not fit in np.float64. This is an exception rather than a rule; often it&#39;s driven by the desire to adapt the dataset to the requirements of the algorithm. Parametric methods usually require a minimum of symmetric and unimodal distribution of data, which is not always given in real data. There may be more stringent requirements; recall <a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-4-linear-classification-and-regression-44a41b9b5220">our earlier article about linear models</a>.</p><p>However, data requirements are imposed not only by parametric methods; <a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-3-classification-decision-trees-and-k-nearest-neighbors-8613c6b6d2cd">K nearest neighbors</a> will predict complete nonsense if features are not normalized e.g. when one distribution is located in the vicinity of zero and does not go beyond (-1, 1) while the other’s range is on the order of hundreds of thousands.</p><p>A simple example: suppose that the task is to predict the cost of an apartment from two variables — the distance from city center and the number of rooms. The number of rooms rarely exceeds 5 whereas the distance from city center can easily be in the thousands of meters.</p><p>The simplest transformation is Standard Scaling (or Z-score normalization):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/142/1*l3GMKe44JVe8YKQi8ZdWgQ.png" /></figure><p>Note that Standard Scaling does not make the distribution normal in the strict sense.</p><pre>In : <strong>from</strong> <strong>sklearn.preprocessing</strong> <strong>import</strong> StandardScaler</pre><pre>In : <strong>from</strong> <strong>scipy.stats</strong> <strong>import</strong> beta</pre><pre>In : <strong>from</strong> <strong>scipy.stats</strong> <strong>import</strong> shapiro</pre><pre>In : data = beta(1, 10).rvs(1000).reshape(-1, 1)</pre><pre>In : shapiro(data) <br>Out: (0.8783774375915527, 3.0409122263582326e-27)</pre><pre><em># Value of the statistic, p-value</em></pre><pre>In : shapiro(StandardScaler().fit_transform(data)) <br>Out: (0.8783774375915527, 3.0409122263582326e-27)</pre><pre><em># With such p-value we&#39;d have to reject the null hypothesis of normality of the data</em></pre><p>But, to some extent, it protects against outliers:</p><pre>In : data = np.array([1, 1, 0, -1, 2, 1, 2, 3, -2, 4, 100]).reshape(-1, 1).astype(np.float64)</pre><pre>In : StandardScaler().fit_transform(data) <br>Out: <br>array([[-0.31922662], <br>       [-0.31922662],<br>       [-0.35434155],<br>       [-0.38945648],<br>       [-0.28411169],<br>       [-0.31922662],<br>       [-0.28411169],<br>       [-0.24899676],<br>       [-0.42457141],<br>       [-0.21388184],<br>       [ 3.15715128]])</pre><pre>In : (data – data.mean()) / data.std() <br>Out: <br>array([[-0.31922662], <br>       [-0.31922662],<br>       [-0.35434155],<br>       [-0.38945648],<br>       [-0.28411169],<br>       [-0.31922662],<br>       [-0.28411169],<br>       [-0.24899676],<br>       [-0.42457141],<br>       [-0.21388184],<br>       [ 3.15715128]])</pre><p>Another fairly popular option is MinMax Scaling, which brings all the points within a predetermined interval (typically (0, 1)).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/282/1*-MkabcMqk1Cm1HjCg59G_Q.png" /></figure><pre>In : <strong>from</strong> <strong>sklearn.preprocessing</strong> <strong>import</strong> MinMaxScaler</pre><pre>In : MinMaxScaler().fit_transform(data) <br>Out: <br>array([[ 0.02941176],<br>       [ 0.02941176],<br>       [ 0.01960784],<br>       [ 0.00980392],<br>       [ 0.03921569],<br>       [ 0.02941176],<br>       [ 0.03921569],<br>       [ 0.04901961],<br>       [ 0. ],<br>       [ 0.05882353],<br>       [ 1. ]])</pre><pre>In : (data – data.min()) / (data.max() – data.min()) <br>Out: <br>array([[ 0.02941176],<br>       [ 0.02941176],<br>       [ 0.01960784],<br>       [ 0.00980392],<br>       [ 0.03921569],<br>       [ 0.02941176],<br>       [ 0.03921569],<br>       [ 0.04901961],<br>       [ 0. ],<br>       [ 0.05882353],<br>       [ 1. ]])</pre><p>StandardScaling and MinMax Scaling have similar applications and are often more or less interchangeable. However, if the algorithm involves the calculation of distances between points or vectors, the default choice is StandardScaling. But MinMax Scaling is useful for visualization by bringing features within the interval (0, 255).</p><p>If we assume that some data is not normally distributed but is described by the <a href="https://ru.wikipedia.org/wiki/%D0%9B%D0%BE%D0%B3%D0%BD%D0%BE%D1%80%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D1%80%D0%B0%D1%81%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5">log-normal distribution</a>, it can easily be transformed to a normal distribution:</p><pre>In : <strong>from</strong> <strong>scipy.stats</strong> <strong>import</strong> lognorm</pre><pre>In : data = lognorm(s=1).rvs(1000)</pre><pre>In : shapiro(data)<br>Out: (0.05714237689971924, 0.0)</pre><pre>In : shapiro(np.log(data))<br>Out: (0.9980740547180176, 0.3150389492511749)</pre><p>The lognormal distribution is suitable for describing salaries, price of securities, urban population, number of comments on articles on the internet, etc. However, to apply this procedure, the underlying distribution does not necessarily have to be lognormal; you can try to apply this transformation to any distribution with a heavy right tail. Furthermore, one can try to use other similar transformations, formulating their own hypotheses on how to approximate the available distribution to a normal. Examples of such transformations are <a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.boxcox.html">Box-Cox transformation</a> (logarithm is a special case of the Box-Cox transformation) or <a href="https://gist.github.com/mesgarpour/f24769cd186e2db853957b10ff6b7a95">Yeo-Johnson transformation</a> (extends the range of applicability to negative numbers). In addition, you can also try adding a constant to the feature — np.log (x + const).</p><p>In the examples above, we have worked with synthetic data and strictly tested normality using the Shapiro-Wilk test. Let’s try to look at some real data and test for normality using a less formal method — <a href="https://en.wikipedia.org/wiki/Q%E2%80%93Q_plot">Q-Q plot</a>. For a normal distribution, it will look like a smooth diagonal line, and visual anomalies should be intuitively understandable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*AQtlE1Lo2GJjS6L2." /></figure><p><em>Q-Q plot for lognormal distribution</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*_BEpFh1qqEn1yxvf." /></figure><p><em>Q-Q plot for the same distribution after taking the logarithm</em></p><pre><em># Let&#39;s draw plots!</em><br>In : <strong>import</strong> <strong>statsmodels.api</strong> <strong>as</strong> <strong>sm</strong></pre><pre>Let&#39;s take the price feature from Renthop dataset and filter by hands the most extreme values for clarity</pre><pre>In : price = df.price[(df.price &lt;= 20000) &amp; (df.price &gt; 500)]</pre><pre>In : price_log = np.log(price)</pre><pre>In : price_mm = MinMaxScaler().fit_transform(price.values.reshape(-1, 1).astype(np.float64)).flatten()</pre><pre>A lot of gestures so that sklearn didn&#39;t shower us with warnings</pre><pre>In : price_z = StandardScaler().fit_transform(price.values.reshape(-1, 1).astype(np.float64)).flatten()</pre><pre>In : sm.qqplot(price_log, loc=price_log.mean(), scale=price_log.std()).savefig(&#39;qq_price_log.png&#39;)</pre><pre>In : sm.qqplot(price_mm, loc=price_mm.mean(), scale=price_mm.std()).savefig(&#39;qq_price_mm.png&#39;)</pre><pre>In : sm.qqplot(price_z, loc=price_z.mean(), scale=price_z.std()).savefig(&#39;qq_price_z.png&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*Sa6wnbq4GNZY4kIP." /></figure><p><em>Q-Q plot of the initial feature</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*YU-B3B5n3NoVDMmF." /></figure><p><em>Q-Q plot after StandardScaler. Shape doesn’t change</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*bbF0MNcLyvls0dSC." /></figure><p><em>Q-Q plot after MinMaxScaler. Shape doesn’t change</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*Jt57qDq80k1yr5Sv." /></figure><p><em>Q-Q plot after taking the logarithm. Things are getting better!</em></p><p>Let’s see whether transformations can somehow help the real model. There is no silver bullet here.</p><h3>2.2. Interactions</h3><p>If previous transformations seemed rather math-driven, this part is more about the nature of the data; it can be attributed to both feature transformations and feature creation.</p><p>Let’s come back again to the Two Sigma Connect: Rental Listing Inquiries problem. Among the features in this problem are the number of rooms and the price. Logic suggests that the cost per single room is more indicative than the total cost, so we can generate such a feature.</p><pre>rooms = df[&quot;bedrooms&quot;].apply(<strong>lambda</strong> x: max(x, .5))</pre><pre># Avoid division by zero; .5 <strong>is</strong> chosen more <strong>or</strong> less arbitrarily</pre><pre>df[&quot;price_per_bedroom&quot;] = df[&quot;price&quot;] / rooms</pre><p>You should limit yourself in this process. If there are a limited number of features, it is possible to generate all the possible interactions and then weed out the unnecessary ones using the techniques described in the next section. In addition, not all interactions between features must have a physical meaning; for example, polynomial features (see <a href="http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html">sklearn.preprocessing.PolynomialFeatures</a>) are often used in linear models and are almost impossible to interpret.</p><h3>2.3. Filling in the missing values</h3><p>Not many algorithms can work with missing values, and the real world often provides data with gaps. Fortunately, this is one of the tasks for which one doesn’t need any creativity. Both key python libraries for data analysis provide easy-to-use solutions: <a href="http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.fillna.html">pandas.DataFrame.fillna</a> and <a href="http://scikit-learn.org/stable/modules/preprocessing.html#imputation">sklearn.preprocessing.Imputer</a>.</p><p>These solutions do not have any magic happening behind the scenes. Approaches to handling missing values are pretty straightforward:</p><ul><li>encode missing values with a separate blank value like &quot;n/a&quot; (for categorical variables);</li><li>use the most probable value of the feature (mean or median for the numerical variables, the most common value for categorical variables);</li><li>or, conversely, encode with some extreme value (good for decision-tree models since it allows the model to make a partition between the missing and non-missing values);</li><li>for ordered data (e.g. time series), take the adjacent value — next or previous.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/802/0*Ps-v8F0fBgmnG36S." /></figure><p>Easy-to-use library solutions sometimes suggest sticking to something like df = df.fillna(0) and not sweat the gaps. But this is not the best solution: data preparation takes more time than building models, so thoughtless gap-filling may hide a bug in processing and damage the model.</p><h3>3. Feature selection</h3><p>Why would it even be necessary to select features? To some, this idea may seem counterintuitive, but there are at least two important reasons to get rid of unimportant features. The first is clear to every engineer: the more data, the higher the computational complexity. As long as we work with toy datasets, the size of the data is not a problem, but, for real loaded production systems, hundreds of extra features will be quite tangible. The second reason is that some algorithms take noise (non-informative features) as a signal and overfit.</p><h3>3.1. Statistical approaches</h3><p>The most obvious candidate for removal is a feature whose value remains unchanged, i.e., it contains no information at all. If we build on this thought, it is reasonable to say that features with low variance are worse than those with high variance. So, one can consider cutting features with variance below a certain threshold.</p><pre>In : <strong>from</strong> <strong>sklearn.feature_selection</strong> <strong>import</strong> VarianceThreshold</pre><pre>In : <strong>from</strong> <strong>sklearn.datasets</strong> <strong>import</strong> make_classification</pre><pre>In : x_data_generated, y_data_generated = make_classification()</pre><pre>In : x_data_generated.shape Out: (100, 20)</pre><pre>In : VarianceThreshold(.7).fit_transform(x_data_generated).shape Out: (100, 19)</pre><pre>In : VarianceThreshold(.8).fit_transform(x_data_generated).shape Out: (100, 18)</pre><pre>In : VarianceThreshold(.9).fit_transform(x_data_generated).shape Out: (100, 15)</pre><p>There are other ways that are also <a href="http://scikit-learn.org/stable/modules/feature_selection.html#univariate-feature-selection">based on classical statistics</a>.</p><pre>In : <strong>from</strong> <strong>sklearn.feature_selection</strong> <strong>import</strong> SelectKBest, f_classif</pre><pre>In : x_data_kbest = SelectKBest(f_classif, k=5).fit_transform(x_data_generated, y_data_generated)</pre><pre>In : x_data_varth = VarianceThreshold(.9).fit_transform(x_data_generated)</pre><pre>In : <strong>from</strong> <strong>sklearn.linear_model</strong> <strong>import</strong> LogisticRegression</pre><pre>In : <strong>from</strong> <strong>sklearn.model_selection</strong> <strong>import</strong> cross_val_score</pre><pre>In : cross_val_score(LogisticRegression(), x_data_generated, y_data_generated, scoring=&#39;neg_log_loss&#39;).mean() Out: -0.45367136377981693</pre><pre>In : cross_val_score(LogisticRegression(), x_data_kbest, y_data_generated, scoring=&#39;neg_log_loss&#39;).mean() Out: -0.35775228616521798</pre><pre>In : cross_val_score(LogisticRegression(), x_data_varth, y_data_generated, scoring=&#39;neg_log_loss&#39;).mean() Out: -0.44033042718359772</pre><p>We can see that our selected features have improved the quality of the classifier. Of course, this example is <em>purely</em> artificial; however, it is worth using for real problems.</p><h3>3.2. Selection by modeling</h3><p>Another approach is to use some baseline model for feature evaluation because the model will clearly show the importance of the features. Two types of models are usually used: some “wooden” composition such as <a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-5-ensembles-of-algorithms-and-random-forest-8e05246cbba7">Random Forest</a> or a linear model with Lasso regularization so that it is prone to nullify weights of weak features. The logic is intuitive: if features are clearly useless in a simple model, there is no need to drag them to a more complex one.</p><pre><em># Synthetic example</em></pre><pre><strong>from</strong> <strong>sklearn.datasets</strong> <strong>import</strong> make_classification<br><strong>from</strong> <strong>sklearn.linear_model</strong> <strong>import</strong> LogisticRegression<br><strong>from</strong> <strong>sklearn.ensemble</strong> <strong>import</strong> RandomForestClassifier<br><strong>from</strong> <strong>sklearn.feature_selection</strong> <strong>import</strong> SelectFromModel<br><strong>from</strong> <strong>sklearn.model_selection</strong> <strong>import</strong> cross_val_score<br><strong>from</strong> <strong>sklearn.pipeline</strong> <strong>import</strong> make_pipeline</pre><pre>x_data_generated, y_data_generated = make_classification()</pre><pre>pipe = make_pipeline(SelectFromModel(estimator=RandomForestClassifier()), LogisticRegression())</pre><pre>lr = LogisticRegression()<br>rf = RandomForestClassifier()</pre><pre><strong>print</strong>(cross_val_score(lr, x_data_generated, y_data_generated, scoring=&#39;neg_log_loss&#39;).mean())<br><strong>print</strong>(cross_val_score(rf, x_data_generated, y_data_generated, scoring=&#39;neg_log_loss&#39;).mean())<br><strong>print</strong>(cross_val_score(pipe, x_data_generated, y_data_generated, scoring=&#39;neg_log_loss&#39;).mean())</pre><pre># -0.455661299521484<br># -0.6817083161800542<br># -0.28572375767268404</pre><p>We must not forget that this is not a silver bullet again — it can make the performance worse.</p><pre>x_data, y_data = get_data() x_data = x_data.values</pre><pre>pipe1 = make_pipeline(StandardScaler(), SelectFromModel(estimator=RandomForestClassifier()), LogisticRegression())</pre><pre>pipe2 = make_pipeline(StandardScaler(), LogisticRegression())</pre><pre>rf = RandomForestClassifier()</pre><pre><strong>print</strong>(&#39;LR + selection: &#39;, cross_val_score(pipe1, x_data, y_data, scoring=&#39;neg_log_loss&#39;).mean())<br><strong>print</strong>(&#39;LR: &#39;, cross_val_score(pipe2, x_data, y_data, scoring=&#39;neg_log_loss&#39;).mean())<br><strong>print</strong>(&#39;RF: &#39;, cross_val_score(rf, x_data, y_data, scoring=&#39;neg_log_loss&#39;).mean())</pre><pre># LR + selection: -0.714208124619<br># LR: -0.669572736183</pre><pre># It got worse!</pre><pre># RF: -2.13486716798</pre><h3>3.3. Grid search</h3><p>Finally, we get to the most reliable method, which is also the most computationally complex: trivial grid search. Train a model on a subset of features, store results, repeat for different subsets, and compare the quality of models to identify the best feature set. This approach is called <a href="http://rasbt.github.io/mlxtend/user_guide/feature_selection/ExhaustiveFeatureSelector/">Exhaustive Feature Selection</a>.</p><p>Searching all combinations usually takes too long, so you can try to reduce the search space. Fix a small number N, iterate through all combinations of N features, choose the best combination, and then iterate through the combinations of (N + 1) features so that the previous best combination of features is fixed and only a single new feature is considered. It is possible to iterate until we hit a maximum number of characteristics or until the quality of the model ceases to increase significantly. This algorithm is called <a href="http://rasbt.github.io/mlxtend/user_guide/feature_selection/SequentialFeatureSelector/">Sequential Feature Selection</a>.</p><p>This algorithm can be reversed: start with the complete feature space and remove features one by one until it does not impair the quality of the model or until the desired number of features is reached.</p><pre>In : selector = SequentialFeatureSelector(LogisticRegression(), scoring=&#39;neg_log_loss&#39;, verbose=2, k_features=3, forward=False, n_jobs=-1)</pre><pre>In : selector.fit(x_data_scaled, y_data)</pre><pre>In : selector.fit(x_data_scaled, y_data)</pre><pre>[2017-03-30 01:42:24] Features: 45/3 -- score: -0.682830838803 [2017-03-30 01:44:40] Features: 44/3 -- score: -0.682779463265 [2017-03-30 01:46:47] Features: 43/3 -- score: -0.682727480522 [2017-03-30 01:48:54] Features: 42/3 -- score: -0.682680521828 [2017-03-30 01:50:52] Features: 41/3 -- score: -0.68264297879<br>[2017-03-30 01:52:46] Features: 40/3 -- score: -0.682607753617 [2017-03-30 01:54:37] Features: 39/3 -- score: -0.682570678346 [2017-03-30 01:56:21] Features: 38/3 -- score: -0.682536314625 [2017-03-30 01:58:02] Features: 37/3 -- score: -0.682520258804 [2017-03-30 01:59:39] Features: 36/3 -- score: -0.68250862986<br>[2017-03-30 02:01:17] Features: 35/3 -- score: -0.682498213174<br>...<br>[2017-03-30 02:21:09] Features: 10/3 -- score: -0.68657335969<br>[2017-03-30 02:21:18] Features: 9/3 -- score: -0.688405548594<br>[2017-03-30 02:21:26] Features: 8/3 -- score: -0.690213724719<br>[2017-03-30 02:21:32] Features: 7/3 -- score: -0.692383588303<br>[2017-03-30 02:21:36] Features: 6/3 -- score: -0.695321584506<br>[2017-03-30 02:21:40] Features: 5/3 -- score: -0.698519960477<br>[2017-03-30 02:21:42] Features: 4/3 -- score: -0.704095390444<br>[2017-03-30 02:21:44] Features: 3/3 -- score: -0.713788301404</pre><pre>But improvement couldn’t last forever</pre><p>Take a look how this approach was done in one <a href="https://www.kaggle.com/arsenyinfo/easy-feature-selection-pipeline-0-55-at-lb">simple yet elegant Kaggle kernel</a>.</p><h3>4. Assignment #6</h3><p>Full versions of assignments are announced each week in a new run of the course (October 1, 2018). Meanwhile, you can practice with a demo version: <a href="https://www.kaggle.com/kashnitsky/assignment-6-linear-models-and-rf-for-regression">Kaggle Kernel</a>, <a href="https://mlcourse.ai/notebooks/blob/master/jupyter_english/assignments_demo/assignment06_regression_wine.ipynb?flush_cache=true">nbviewer</a>. We don’t have a separate assignment on feature engineering (this is done in Kaggle Inclass competitions) so this demo assignment is on regression due.</p><p>Open Data Science wishes you good luck with the assignment as well as clean data and useful features in your real work!</p><p><em>Author: </em><a href="http://arseny.info/pages/about-me.html"><em>Arseny Kravchenko</em></a><em>. Translated and edited by </em><a href="https://www.linkedin.com/in/christinabutsko/"><em>Christina Butsko</em></a><em>, </em><a href="https://www.linkedin.com/in/festline/"><em>Yury Kashnitskiy</em></a><em>, </em><a href="https://www.linkedin.com/in/anna-larionova-74434689/"><em>Anna Larionova</em></a><em>, </em><a href="https://www.linkedin.com/in/anastasiamanokhina/"><em>Anastasia Manokhina</em></a><em>, </em><a href="https://www.linkedin.com/in/evgenysushko/"><em>Evgeny Sushko</em></a><em>, </em><a href="https://www.linkedin.com/in/egor-polusmak/"><em>Egor Polusmak</em></a><em>, and </em><a href="https://www.linkedin.com/in/yuanyuanpao/"><em>Yuanyuan Pao</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8b94f870706a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-6-feature-engineering-and-feature-selection-8b94f870706a">Open Machine Learning Course. Topic 6. Feature Engineering and Feature Selection</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 5. Bagging and Random Forest]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-5-ensembles-of-algorithms-and-random-forest-8e05246cbba7?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/8e05246cbba7</guid>
            <category><![CDATA[scikit-learn]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[random-forest]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[education]]></category>
            <dc:creator><![CDATA[Yury Kashnitsky]]></dc:creator>
            <pubDate>Mon, 05 Mar 2018 11:28:52 GMT</pubDate>
            <atom:updated>2018-09-19T09:08:54.419Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*UjG0Pj8K0DVaF-nDRcczGg.jpeg" /><figcaption>Source: <a href="https://music.yandex.ua/artist/3177683">https://music.yandex.ua/artist/3177683</a></figcaption></figure><p>In the previous articles, you saw different classification algorithms as well as techniques for how to properly validate and evaluate the quality of your models.</p><p>Now, suppose that you have chosen the best possible model for a particular problem and are struggling to further improve its accuracy. In this case, you would need to apply some more advanced machine learning techniques that are collectively referred to as <em>ensembles</em>.</p><p>An <em>ensemble</em> is a set of elements that collectively contribute to a whole. A familiar example is a musical ensemble, which blends the sounds of several musical instruments to create a beautiful harmony, or architectural ensembles, which are a set of buildings designed as a unit. In ensembles, the (whole) harmonious outcome is more important than the performance of any individual part.</p><h3>Article outline</h3><ol><li>Ensembles</li><li>Bootstraping</li><li>Bagging</li><li>Out-of-bag error</li><li>Random Forest</li><li>Feature importance</li><li>Assignment #5</li><li>Useful resources</li></ol><h3>1. Ensembles</h3><p><a href="https://en.wikipedia.org/wiki/Condorcet%27s_jury_theorem">Condorcet’s jury theorem</a> (1784) is about an ensemble in some sense. It states that, if each member of the jury makes an independent judgement and the probability of the correct decision by each juror is more than 0.5, then the probability of the correct decision by the whole jury increases with the total number of jurors and tends to one. On the other hand, if the probability of being right is less than 0.5 for each juror, then the probability of the correct decision by the whole jury decreases with the number of jurors and tends to zero.</p><p>One can find an analytic expression for this theorem in a <a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic05_ensembles_random_forests/topic5_part1_bagging.ipynb">Jupyter notebook</a> accompanying this article.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/350/1*4yO6SiVp1fhhINRkVMoBXQ.png" /></figure><p>Let’s look at another example of ensembles: an observation known as <a href="https://en.wikipedia.org/wiki/Wisdom_of_the_crowd">Wisdom of the crowd</a>. In 1906, <a href="https://en.wikipedia.org/wiki/Francis_Galton">Francis Galton</a> visited a country fair in Plymouth where he saw a contest being held for farmers. 800 participants tried to estimate the weight of a slaughtered bull. The real weight of the bull was 1198 pounds. Although none of the farmers could guess the exact weight of the animal, the average of their predictions was 1197 pounds.</p><p>A similar idea for error reduction was adopted in the field of Machine Learning.</p><h3>2. Bootstraping</h3><p><em>Bagging</em> (also known as <a href="https://en.wikipedia.org/wiki/Bootstrap_aggregating">Bootstrap aggregation</a>) is one of the first and most basic ensemble techniques. It was proposed by <a href="https://en.wikipedia.org/wiki/Leo_Breiman">Leo Breiman</a> in 1994. Bagging is based on the statistical method of <a href="https://en.wikipedia.org/wiki/Bootstrapping_(statistics)">bootstraping</a>, which makes the evaluation of many statistics of complex models feasible.</p><p>The bootstrap method goes as follows. Let there be a sample <strong>X</strong> of size <strong>N</strong>. We can make a new sample from the original sample by drawing <strong>N</strong> elements from the latter randomly and uniformly, with replacement. In other words, we select a random element from the original sample of size <strong>N</strong> and do this <strong>N</strong> times. All elements are equally likely to be selected, thus each element is drawn with the equal probability <strong>1/N</strong>.</p><p>Let’s say we are drawing balls from a bag one at a time. At each step, the selected ball is put back into the bag so that the next selection is made equiprobably i.e. from the same number of balls <strong>N</strong>. Note that, because we put the balls back, there may be duplicates in the new sample. Let’s call this new sample <strong>X1</strong>.</p><p>By repeating this procedure <strong>M</strong> times, we create <strong>M</strong> <em>bootstrap samples</em> <strong>X1</strong>, …, <strong>XM</strong>. In the end, we have a sufficient number of samples and can compute various statistics of the original distribution.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ei3eNxEKrPm7qpcDXsW_MA.png" /></figure><p>For our example, we’ll use the familiar telecom_churn dataset. Previously, when we discussed feature importance, we saw that one of the most important features in this dataset is the number of calls to customer service. Let&#39;s visualize the data and look at the distribution of this feature.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/61bdc4bfec667cf4fec6f2d0ca159467/href">https://medium.com/media/61bdc4bfec667cf4fec6f2d0ca159467/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/392/1*_6OveFIQSEDwEUYB6puLYA.png" /></figure><p>As you can see, loyal customers make fewer calls to customer service than those who eventually left. Now, it might be a good idea to estimate the average number of customer service calls in each group. Since our dataset is small, we would not get a good estimate by simply calculating the mean of the original sample. We will be better off applying the bootstrap method. Let’s generate 1000 new bootstrap samples from our original population and produce an interval estimate of the mean.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a3a07964281119edac55ca22ff052313/href">https://medium.com/media/a3a07964281119edac55ca22ff052313/href</a></iframe><pre>Service calls from loyal: mean interval [1.4077193 1.49473684] # Service calls from churn: mean interval [2.0621118 2.39761905]</pre><p>In the end, we see that, with 95% probability, the average number of customer service calls from loyal customers lies between 1.4 and 1.49 while the churned clients called 2.06 through 2.40 times on average. Also, note that the interval for the loyal customers is narrower, which is reasonable since they make fewer calls (0, 1 or 2) in comparison with the churned clients who called until they became fed up and switched providers.</p><h3>3. Bagging</h3><p>Now that you’ve grasped the idea of bootstrapping, we can move on to <em>bagging</em>.</p><p>Suppose that we have a training set <strong>X</strong>. Using bootstrapping, we generate samples <strong>X1</strong>, …, <strong>XM</strong>. Now, for each bootstrap sample, we train its own classifier <strong>ai(x)</strong>. The final classifier will average the outputs from all these individual classifiers. In the case of classification, this technique corresponds to voting:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/289/1*rLYD-kdEZT3G0nKtu3P3tw.png" /></figure><p>The picture below illustrates this algorithm:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/719/1*0ttjb_fFpZ8YhYu5P0Mj2w.png" /></figure><p>In a regression problem, by averaging individual answers, bagging reduces the mean squared error by a factor of <strong>M</strong>, the number of regressors. See the mathematical proof in our <a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic05_ensembles_random_forests/topic5_part1_bagging.ipynb">Jupyter notebook</a>.</p><p>From our previous lesson, let’s recall the components that make up the total out-of-sample error:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/690/1*Ej9U_mk6ESXWZnjKrSMrqg.png" /></figure><p>Bagging reduces the variance of a classifier by decreasing the difference in error when we train the model on different datasets. In other words, bagging prevents overfitting. The efficacy of bagging comes from the fact that individual models are quite different due to the different training data and their errors cancel out during voting. Additionally, outliers are likely omitted in some of the training bootstap samples.</p><p>The scikit-learn library supports bagging with meta-estimators BaggingRegressor and BaggingClassifier. You can use most of the algorithms as a base.</p><p>Let’s examine how bagging works in practice and compare it with the decision tree. For this, we will use an example from <a href="http://scikit-learn.org/stable/auto_examples/ensemble/plot_bias_variance.html#sphx-glr-auto-examples-ensemble-plot-bias-variance-py">sklearn’s documentation</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/987/1*8MtnGc9qkuGzE6-pSRokFQ.png" /></figure><p>The error for the decision tree:</p><p>0.0255 = 0.0003<em> (bias²)</em>+ 0.0152 <em>(variance)</em> + 0.0098 <em>(σ²)</em></p><p>The error when using bagging:</p><p>0.0196 = 0.0004 <em>(bias²) </em>+ 0.0092 <em>(variance) </em>+ 0.0098 (<em>σ</em>²)</p><p>As you can see from the graph above, the variance in the error is much lower for bagging. Remember that we have already proved this theoretically.</p><p>Bagging is effective on small datasets. Dropping even a small part of training data leads to constructing substantially different base classifiers. If you have a large dataset, you would generate bootstrap samples of a much smaller size.</p><p>The example above is unlikely to be applicable to any real work. This is because we made a strong assumption that our individual errors are uncorrelated. More often than not, this is way too optimistic for real-world applications. When this assumption is false, the reduction in error will not be as significant. In the following lectures, we will discuss some more sophisticated ensemble methods, which enable more accurate predictions in real-world problems.</p><h3>4. Out-of-bag error</h3><p>Looking ahead, in case of Random Forest, there is no need to use cross-validation or hold-out samples in order to get an unbiased error estimation. Why? Because, in ensemble techniques, the error estimation takes place internally.</p><p>Random trees are constructed using different bootstrap samples of the original dataset. Approximately 37% of inputs are left out of a particular bootstrap sample and are not used in the construction of the <strong>K</strong>-th tree. The proof is easy and elegant, it’s done in our <a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic05_ensembles_random_forests/topic5_part1_bagging.ipynb">Jupyter notebook</a>.</p><p>Let’s visualize how <strong>O</strong>ut-<strong>o</strong>f-<strong>B</strong>ag <strong>E</strong>rror (or OOBE) estimation works:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HojoYXBU_DTnMuCatCnQaQ.png" /></figure><p>The top part of the figure above represents our original dataset. We split it into the training (left) and test (right) sets. In the left image, we draw a grid that perfectly divides our dataset according to classes. Now, we use the same grid to estimate the share of the correct answers on our test set. We can see that our classifier gave incorrect answers in those 4 cases that have not been used during training (on the left). Hence, the accuracy of our classifier is 11/15*100% = 73.33%.</p><p>To sum up, each base algorithm is trained on ~ 63% of the original examples. It can be validated on the remaining ~37%. The Out-of-Bag estimate is nothing more than the mean estimate of the base algorithms on those ~37% of inputs that were left out of training.</p><h3>5. Random Forest</h3><p>Leo Breiman managed to apply bootstrapping not only in statistics but also in machine learning. He, along with Adel Cutler, extended and improved the Random Forest algorithm <a href="http://ect.bell-labs.com/who/tkh/publications/papers/odt.pdf">proposed by Tin Kam Ho</a>. They combined the construction of uncorrelated trees using <a href="https://en.wikipedia.org/wiki/Predictive_analytics#Classification_and_regression_trees_.28CART.29">CART</a>, bagging, and the <a href="https://en.wikipedia.org/wiki/Random_subspace_method">random subspace method</a>.</p><p>Decision trees are a good choice for the base classifier in bagging because they are quite sophisticated and can achieve zero classification error on any sample. The random subspace method reduces the correlation between the trees and thus prevents overfitting. With bagging, the base algorithms are trained on different random subsets of the original feature set.</p><p>The following algorithm constructs an ensemble of models using the random subspace method:</p><ol><li>Let the number of instance be equal to <strong>n</strong>, and the number of feature dimensions be equal to <strong>d</strong>.</li><li>Choose <strong>M</strong> as the number of individual models in the ensemble.</li><li>For each model <strong>m</strong>, choose the number of features <strong>dm &lt; d</strong>. As a rule, the same value of <strong>dm</strong> is used for all the models.</li><li>For each model <strong>m</strong>, create a training set by selecting <strong>dm</strong> features at random from the whole set of <strong>d</strong> features.</li><li>Train each model.</li><li>Apply the resulting ensemble model to a new input by combining the results from all the models in <strong>M</strong>. You can use either majority voting or aggregation of the posterior probabilities.</li></ol><h3>Outline of part 5</h3><ol><li>Algorithm</li><li>Comparison with Decision Trees and Bagging</li><li>Parameters</li><li>Extremely Randomized Trees</li><li>Transformation of a dataset into a high-dimensional representation</li><li>Pros and cons of Random Forests</li></ol><h3>5.1. Algorithm</h3><p>The algorithm for constructing a random forest of <strong>N</strong> trees goes as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/936/1*aPIZTydKDpRZB1ZYlAIrGA.png" /></figure><p>The final classifier is defined by:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/275/1*6r3fDoejUbGDSmxcWKrdOA.png" /></figure><p>We use the majority voting for classification and the mean for regression.</p><p>For classification problems, it is advisable to set <strong>m</strong> to be equal the square root of <strong>d</strong>. For regression problems, we usually take <strong>m = d/3</strong>, where <strong>d </strong>is the number of features. It is recommended to build each tree until all of its leaves contain only 1 instance for classification and 5 instances for regression.</p><p>You can see Random Forest as bagging of decision trees with the modification of selecting a random subset of features at each split.</p><h3>5.2. Comparison with Decision Trees and Bagging</h3><p><em>See the code in this </em><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic05_ensembles_random_forests/topic5_part2_random_forest.ipynb?flush_cache=true"><em>Jupyter notebook</em></a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/613/1*otfwdhIVioeaORbqtAT8cw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/613/1*-dDKRTpnO8vhpsF9YXBZBA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/613/1*N_ZjnoM0NAOc-fCOZoG81A.png" /></figure><p>As we can see from our graphs and the MSE values above, a Random Forest of 10 trees achieves a better result than a single decision tree and is comparable to bagging with 10 trees. The main difference between Random Forests and bagging is that, in a Random Forest, the best feature for a split is selected from a random subset of the available features while, in bagging, all features are considered for the next best split.</p><p>We can also look at the advantages of Random Forests and bagging in classification problems.</p><p><em>See the code in this </em><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic05_ensembles_random_forests/topic5_part2_random_forest.ipynb"><em>Jupyter notebook</em></a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/384/1*sHjCvQZlGp6n3VZoEzYN6g.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/384/1*6H3JNu52L-gX19BJ-MaAlw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/384/1*2pBSIwt_5bLpmaCGKCZRvQ.png" /></figure><p>The figures above show that the decision boundary of the decision tree is quite jagged and has a lot of acute angles that suggest overfitting and a weak ability to generalize. We would have trouble making reliable predictions on new test data. In contrast, the bagging algorithm has a rather smooth boundary and has no obvious signs of overfitting.</p><p>Now, let’s investigate some parameters which can help us increase the model accuracy.</p><h3>5.3. Parameters</h3><p>The <a href="http://scikit-learn.org/stable/">scikit-learn library</a> implements random forests by providing two estimators: RandomForestClassifier and RandomForestRegressor.</p><p>Below are the parameters which we need to pay attention to when we are building a new model:</p><ul><li>n_estimators is the number of trees in the forest;</li><li>criterion is the function used to measure the quality of a split;</li><li>max_features is the number of features to consider when looking for the best split;</li><li>min_samples_leaf is the minimum number of samples required to be at a leaf node;</li><li>max_depth is the maximum depth of the tree.</li></ul><p>Please refer to this <a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic05_ensembles_random_forests/topic5_part2_random_forest.ipynb">Jupyter notebook</a> to find an example of Random Forest tuning in a telecom churn problem. We get the following validation curves.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/1*hGjQ-MB05l4mHi5E9pcAtA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*4PbMDRY1BFMyglZ0jKQ9Fg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/1*_QRLKbiKWA6k5S8xJqmE3w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/510/1*Ll3bloqwR_gPnGqGnqEJ-g.png" /></figure><p>The most important fact about Random Forests is that it’s accuracy doesn’t decrease when we add trees, so the number of trees is not a <em>compexity </em>hyperparameter as opposed to max_depth and min_samples_leaf. This means that you can tune hyperparameters with, say, 10 trees, and then increase the number of trees till 500 and be sure that accuracy will only get better.</p><p>In <a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic05_ensembles_random_forests/topic5_part2_random_forest.ipynb">this notebook</a> you’ll also find more analysis of bias and variance for Random Forests and it’s analogies with k Nearset Neighbors.</p><h3>5.4. Extremely Randomized Trees</h3><p>Extremely Randomized Trees employ a greater degree of randomization at the cut-point choice when splitting a tree node. As in Random Forests, a random subset of features is used. But, instead of the search for optimal thresholds, their values are selected at random for each possible feature, and the best one among these randomly generated thresholds is used as the best rule to split the node. This usually trades off a slight reduction in the model variance with a small increase of bias.</p><p>In the scikit-learn library, there are 2 implementations of Extremely Randomized Trees: <a href="http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesClassifier.html#sklearn.ensemble.ExtraTreesClassifier">ExtraTreesClassifier</a> and <a href="http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.ExtraTreesRegressor.html#sklearn.ensemble.ExtraTreesRegressor">ExtraTreesRegressor</a>.</p><p>This method should be used if you have greatly overfit with Random Forests or gradient boosting.</p><h3>5.5. Transformation of a dataset into a high-dimensional representation</h3><p>Random forests are mostly used in supervised learning, but there is a way to apply them in the unsupervised setting.</p><p>Using the scikit-learn method <a href="http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomTreesEmbedding.html#sklearn.ensemble.RandomTreesEmbedding">RandomTreesEmbedding</a>, we can transform our dataset into a high-dimensional, <a href="https://en.wikipedia.org/wiki/Sparse_matrix">sparse</a> representation. We first build extremely randomized trees and then use the index of the leaf containing the example as a new feature.</p><p>For example, if the input appears in the first leaf, we assign 1 as the feature value; if not, we assign 0. This is a so-called <em>binary coding</em>. We can control the number of features and the sparseness of data by increasing or decreasing the number of trees and their depth. Because nearby data points are likely to fall into the same leaf, this transformation provides an implicit nonparametric estimate of their density.</p><h3>5.6. Pros and cons of Random Forests</h3><h4>Pros:</h4><ul><li>High prediction accuracy; will perform better than linear algorithms in most problems; the accuracy is comparable with that of boosting;</li><li>Robust to outliers, thanks to random sampling;</li><li>Insensitive to feature scaling as well as any other monotonic transformations due to the random subspace selection;</li><li>Doesn’t require fine-grained parameter tuning, works quite well out-of-the-box. With tuning, it is possible to achieve a 0.5–3% gain in accuracy, depending on the problem setting and data;</li><li>Efficient for datasets with a large number of features and classes;</li><li>Handles both continuous and discrete variables equally well;</li><li>Rarely overfits. In practice, an increase in the tree number almost always improves the composition. But, after reaching a certain number of trees, the learning curve is very close to the asymptote;</li><li>There are developed methods to estimate feature importance;</li><li>Works well with missing data and maintains good accuracy levels even when a large part of data is missing;</li><li>Provides means to weight classes on the whole dataset as well as for each tree sample;</li><li>Under the hood, calculates proximities between pairs of instamces that can subsequantly be used in clustering, outlier detection, or interesting data representations;</li><li>The above functionality and properties may be extended to unlabeled data to enable unsupervised clustering, data visualization, and outlier detection;</li><li>Easily parallelized and highly scalable.</li></ul><h4>Cons:</h4><ul><li>In comparison with a single decision tree, Random Forest’s output is more difficult to interpret.</li><li>There are no formal p-values for feature importance estimation.</li><li>Performs worse than linear methods in the case of sparse data: text inputs, bag of words, etc.;</li><li>Unlike linear regression, Random Forest is unable to extrapolate. But, this can be also regarded as an advantage because outliers do not cause extreme values in Random Forests;</li><li>Prone to overfitting in some problems, especially, when dealing with noisy data;</li><li>In the case of categorical variables with varying level numbers, Random Forests favor variables with a greater number of levels. The tree will fit more towards a feature with many levels because this gains greater accuracy;</li><li>If a dataset contains groups of correlated features with similar importance for predicted classes, then the preference will be given to smaller groups;</li><li>The resulting model is large and requires a lot of RAM.</li></ul><h3>6. Feature importance</h3><p>Pretty often, you would like to make out the exact reasons why the algorithm outputted a particular answer. Or, not being able to understand the algorithm completely, then st lest we’d like to find out which input features contributed the most to the result. With Random Forest, you can obtain such information quite easily.</p><h3>6.1. Essence of the method</h3><p>From the picture below, it is intuitively clear that, in our credit scoring problem, <em>Age</em> is much more important than <em>Income</em>. This can be formally explained using the concept of <em>information gain</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/440/1*gaIruEZ7kHJeLz0bhUo_GQ.png" /></figure><p>In the case of many decision trees (i.e. a Random Forest), the closer the mean position of a feature over all trees to the root, the more important it is for a given classification or regression problem. Gains in the splitting criterion, such as the <em>Gini impurity</em>, obtained at each optimal split in every tree, is a measure of importance that is directly associated with the splitting feature. The value of this score is distinct for each feature and accumulates over all the trees.</p><p>We go a little deeper into details in <a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic05_ensembles_random_forests/topic5_part3_feature_importance.ipynb">this notebook</a>.</p><h3>6.2. Practical example</h3><p><em>The code can also be found in a </em><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic05_ensembles_random_forests/topic5_part3_feature_importance.ipynb"><em>notebook</em></a><em>.</em></p><p>Let’s consider the results of a survey given to visitors of hostels listed on Booking.com and TripAdvisor.com. Our features here are the average ratings for different categories include service quality, room condition, value for money, etc. Our target variable is the hostel’s overall rating on the website.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/890/1*jS2Xb2wIe4HMH1WENvA6IQ.png" /></figure><p>The picture above shows that, more often than not, customers pay great attention to staff and the price-quality ratio. This couple of factors affects the resulting overall rating the most. But, the difference between these two features and other festures is not very large. We can therefore conclude that exclusion of any of these features will lead to a reduction in model accuracy. Based on our analysis, we csn recommend hostel owners to focus primarily on staff training and price-to-quality ratio.</p><h3>7. Assignment #5</h3><p>Full versions of assignments are announced each week in a new run of the course (October 1, 2018). Meanwhile, you can practice with a demo version: <a href="https://www.kaggle.com/kashnitsky/assignment-5-logit-and-rf-for-credit-scoring">Kaggle Kernel</a>, <a href="https://mlcourse.ai/notebooks/blob/master/jupyter_english/assignments_demo/assignment05_logit_rf_credit_scoring.ipynb?flush_cache=true">nbviewer</a>.</p><h3>8. Useful resources</h3><ul><li>Chapter 15 of the <a href="https://statweb.stanford.edu/%7Etibs/ElemStatLearn/">Elements of Statistical Learning</a> by Jerome H. Friedman, Robert Tibshirani, and Trevor Hastie.</li><li>More about practical applications of random forests and other algorithms can be found in the <a href="http://scikit-learn.org/stable/modules/ensemble.html">official documentation</a> of scikit-learn.</li><li>For more in-depth discussion of variance and decorrelation of random forests, see the <a href="https://www.stat.berkeley.edu/%7Ebreiman/randomforest2001.pdf">original paper</a>.</li></ul><p><em>Authors: </em><a href="https://www.linkedin.com/in/festline/"><em>Yury Kashnitskiy</em></a><em>, and </em><a href="https://www.linkedin.com/in/vitaliyradchenk0/"><em>Vitaliy Radchenko</em></a><em>. Translated and edited by </em><a href="https://www.linkedin.com/in/christinabutsko/"><em>Christina Butsko</em></a><em>, </em><a href="http://linkedin.com/in/anna-shirshova-b908458b"><em>Anna Shirshova</em></a><em>, </em><a href="https://www.linkedin.com/in/anastasiamanokhina/"><em>Anastasia Manokhina</em></a><em>, </em><a href="https://www.linkedin.com/in/egor-polusmak/"><em>Egor Polusmak</em></a><em>, and </em><a href="https://www.linkedin.com/in/yuanyuanpao/"><em>Yuanyuan Pao</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8e05246cbba7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-5-ensembles-of-algorithms-and-random-forest-8e05246cbba7">Open Machine Learning Course. Topic 5. Bagging and Random Forest</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 4. Linear Classification and Regression]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-4-linear-classification-and-regression-44a41b9b5220?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/44a41b9b5220</guid>
            <category><![CDATA[education]]></category>
            <category><![CDATA[logistic-regression]]></category>
            <category><![CDATA[linear-regression]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Yury Kashnitsky]]></dc:creator>
            <pubDate>Mon, 26 Feb 2018 11:18:31 GMT</pubDate>
            <atom:updated>2018-09-19T09:03:25.487Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/461/1*_qoaFqMVgjstmtJNp-zNrw.png" /><figcaption>xkcd.com</figcaption></figure><p>Welcome to the 4-th week of our course! Now we will present our most important topic — linear models. If you have your data prepared and want to start training models, then you will most probably first try either linear or logistic regression, depending on your task (regression or classification).</p><p>This week’s material covers both theory of linear models and practical aspects of their usage in real-world tasks. There’s going to be a lot of math in this topic, and we won’t even try to render all the formulas on Medium. Instead, we provide a Jupyter Notebook for each part of this article. In the assignment, you’ll beat two simple benchmarks in a Kaggle competition solving a problem of identifying a user based on her session of visited websites.</p><h3>Article outline</h3><p><strong>Part1. Regression, </strong><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic04_linear_models/topic4_linear_models_part1_mse_likelihood_bias_variance.ipynb?flush_cache=true"><strong>nbviewer</strong></a></p><ul><li>Ordinary Least Squares</li><li>Maximum Likelihood Estimation</li><li>Bias-Variance Decomposition</li><li>Regularization of Linear Regression</li></ul><p><strong>Part 2. Linear Classification, </strong><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic04_linear_models/topic4_linear_models_part2_logit_likelihood_learning.ipynb?flush_cache=true"><strong>nbviewer</strong></a></p><ul><li>Linear Classifier</li><li>Logistic Regression as a Linear Classifier</li><li>Maximum Likelihood Estimation and Logistic Regression</li><li>L2-Regularization of Logistic Loss</li></ul><p><strong>Part 3. An Illustrative Example of Logistic Regression Regularization, </strong><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic04_linear_models/topic4_linear_models_part3_regul_example.ipynb"><strong>nbviewer</strong></a></p><p><strong>Part 4. Where Logistic Regression Is Good and Where It’s Not, </strong><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic04_linear_models/topic4_linear_models_part4_good_bad_logit_movie_reviews_XOR.ipynb"><strong>nbviewer</strong></a></p><ul><li>Analysis of IMDB movie reviews</li><li>XOR-Problem</li></ul><p><strong>Part 5. Validation and Learning Curves, </strong><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic04_linear_models/topic4_linear_models_part5_valid_learning_curves.ipynb"><strong>nbviewer</strong></a></p><ul><li>How much model complexity is needed?</li><li>How much data is needed?</li></ul><p><strong>Part 6. Kaggle Inclass competition “Catch Me If You Can”</strong></p><ul><li>Data Downloading and Transformation</li><li>Sparse Matrices</li><li>Training the first model</li></ul><p><strong>Assignment 4</strong></p><p><strong>Useful resources</strong></p><h3>Part 6. Kaggle Inclass competition “Catch Me If You Can”</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/171/1*gzibko_sbbl3nt1uSkx-YA.jpeg" /></figure><p><a href="https://www.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking2">Competition’s page</a></p><p>We will be solving the intruder detection problem analyzing users’ behavior on the Internet. It is a complicated and interesting problem combining data analysis and behavioral psychology. As an illustration of one of such tasks, Yandex solves the mailbox intruder detection problem based on the user’s behavior patterns. In a nutshell, intruder’s behavior patterns may differ from those of the mailbox owner:</p><ul><li>the breaker may not delete emails right after they are read as the mailbox owner may do;</li><li>the intruder may mark emails and even move the cursor differently;</li><li>etc.</li></ul><p>So the intruder could be detected and thrown out from the mailbox forcing the user to authenticate via SMS-code.</p><p>Similar approaches are being developed in Google Analytics and described in scientific research papers. You can find more on this topic by searching “Traversal Pattern Mining” and “Sequential Pattern Mining”.</p><p>In this competition we are going to solve a similar problem: our algorithm is supposed to analyze the sequence of websites consequently visited by a particular person and predict whether this person is a user named Alice or an intruder (somebody else). As a metric, we will use <a href="https://en.wikipedia.org/wiki/Receiver_operating_characteristic">ROC AUC</a>.</p><h3>Data Downloading and Transformation</h3><p>Register on <a href="http://kaggle.com/">Kaggle</a>, if you have not done it before. Go to the competition <a href="https://www.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking2">page</a> and download the data.</p><p>First, load the training and test sets. Then explore the data and perform a couple of simple exercises:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fdf9a2eb41cee0c863366d93560c542a/href">https://medium.com/media/fdf9a2eb41cee0c863366d93560c542a/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9wnDvav2_KtSycCnFKyg4g.png" /></figure><p>The training dataset contains the following features:</p><ul><li><strong>site1</strong> — id of the first visited website in the session;</li><li><strong>time1</strong> — visiting time for the first website in the session;</li><li>…</li><li><strong>site10</strong> — id of the tenth visited website in the session;</li><li><strong>time10</strong> — visiting time for the tenth website in the session;</li><li><strong>target</strong> — target variable, takes the value of 1 for Alice’s sessions, and 0 for the other users’ sessions.</li></ul><p>User sessions are chosen in such a way that they are not longer than half an hour and/or contain more than ten websites; i.e. a session is considered as ended either if the user has visited ten websites or if the session has lasted over thirty minutes.</p><p>There are some empty values in the table, which means that some sessions contain less than ten websites. Replace empty values with 0 and change column types to integer. Also, load the website dictionary and see how it looks:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/763f0547191d09ca0364f433e7feb0d0/href">https://medium.com/media/763f0547191d09ca0364f433e7feb0d0/href</a></iframe><pre># Websites total: 48371</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/303/1*YnP3cb-w1sT93yK5KvlR-A.png" /></figure><p>In order to train our first model, we need to prepare the data. First of all, exclude the target variable from the training set. Now both training and test sets have the same number of columns, and we can aggregate them into one dataframe. Thus, all transformations will be performed simultaneously on both the training and test datasets.</p><p>On the one hand, it leads to the fact that both of our datasets have one feature space (so you don’t have to worry that you may have forgotten to transform a feature in one of the datasets). On the other hand, the processing time will increase. In case of enormously large sets, it may turn out that it is impossible to transform both datasets simultaneously (and sometimes you have to split your transformations into several stages, separately for the train/test dataset). In our case, we are going to perform all the transformations for the united dataframe at once; and, before training the model or making predictions, we will just use the corresponding part of it.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1bee8d32f46dd4a46481fb209d305d73/href">https://medium.com/media/1bee8d32f46dd4a46481fb209d305d73/href</a></iframe><p>For the sake of simplicity, we will use only the visited websites in the session (and we will not take into account the timestamp features). The point behind this data selection is: <em>Alice has her favorite sites, and the more often you see these sites in the session, the higher the probability that this is an Alice’s session, and vice versa.</em></p><p>Let’s prepare the data. We will keep only the features site1, site2, … , site10 in the dataframe. Keep in mind that missing values were replaced with zeros. Here is how the first rows of the dataframe look like:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1ec854d61eb2b6121a83f017bf810065/href">https://medium.com/media/1ec854d61eb2b6121a83f017bf810065/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/551/1*Q8Ns4vKvUct88nhJHIx4sg.png" /></figure><p>Sessions are the sequences of website indices, and such representation of data is inconvenient for linear methods. According to our hypothesis (Alice has favorite websites), we need to transform this dataframe so that each website has the corresponding feature (column) which value is equal to the number of visits on this website within the session. All of this can be done in two lines:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e0aaf77edcc202f2adc6beacac7f3cfa/href">https://medium.com/media/e0aaf77edcc202f2adc6beacac7f3cfa/href</a></iframe><p>If you understand what just happened here, then you can skip the next section (perhaps, you can handle logistic regression too?). If not, then let us figure it out.</p><h3>Sparse Matrices</h3><p>Let’s estimate how much memory it would require to store our data in the example above. Our united dataframe contains 336 thousand samples of 48 thousand integer features in each. It’s easy to calculate the required amount of memory, roughly:</p><p>336K * 48K * 8 bytes = 16M * 8 bytes = 128 GB</p><p>Obviously, mere mortals don’t have such volumes of memory (strictly speaking, Python may allow you to create such a matrix, but it would not be easy to do anything with it). An interesting fact is that most of the elements of our matrix are zeros. If we counted non-zero elements, then it would make out about 1.8 million, i.е. slightly more than 10% of all matrix elements. Such a matrix, where most elements are zeros, is called <em>sparse</em>, and the ratio between the number of zero elements and the total number of elements is called the <em>sparseness of the matrix</em>.</p><p>To work with such matrices, you can use scipy.sparse library, check the <a href="https://docs.scipy.org/doc/scipy-0.18.1/reference/sparse.html">documentation</a> to understand what possible types of sparse matrices are, how to work with them and in which cases their usage is most effective. You can learn how they are arranged, for example, be reading the Wikipedia <a href="https://en.wikipedia.org/wiki/Sparse_matrix">article</a>. Note that a sparse matrix contains only non-zero elements. Finally, you can get the allocated memory size (significant memory savings are obvious):</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/07bbbb41268280243924c6d1ba6060f4/href">https://medium.com/media/07bbbb41268280243924c6d1ba6060f4/href</a></iframe><pre>1866898 elements * 8 bytes = 14935184 bytes<br>sparse_matrix_size = 14935184 bytes</pre><p>Let’s explore how the matrix with the websites has been formed using a mini example. Suppose we have the following table with user sessions:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/165/1*5y_ytK9bqwOoljTclVxbUQ.png" /></figure><p>There are 3 sessions, and no more than 3 websites in each. Users visited four different sites in total (there are numbers from 1 to 4 in the table cells). And let us assume that:</p><ol><li>vk.com</li><li>habrahabr.ru</li><li>yandex.ru</li><li>ods.ai</li></ol><p>If the user has visited less than 3 websites during the session, the last few values will be zero. We want to convert the original dataframe in such a way that each session would have the corresponding row which shows the number of visits on each particular site; i.e. we want to transform the previous table into the following form:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/319/1*QYGuDprqFUg1ZGQbrgM6-Q.png" /></figure><p>To do this, use the constructor: csr_matrix ((data, indices, indptr)) and create a frequency table (see examples, code and comments on the links above to see how it works). Here, we set all the parameters explicitly for greater clarity:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6d43d1000cc25628ffd34095ff552bfc/href">https://medium.com/media/6d43d1000cc25628ffd34095ff552bfc/href</a></iframe><pre>matrix([[2, 1, 0, 0, 0],<br>          [0, 2, 0, 1, 0],<br>          [0, 0, 1, 1, 1]])</pre><p>As you might have noticed, the number of the columns in the resulting matrix is not four (by the number of different websites), but five. A zero column has been added, which shows on how many sites the session was shorter (in our mini example we took sessions of length 3). This column is excessive and it should be removed from the dataframe.</p><p>Another benefit of using sparse matrices is that there are special implementations of both matrix operations and machine learning algorithms for them, which sometimes allows to significantly accelerate operations due to the data structure peculiarities. This applies to logistic regression as well. Now, everything is ready to build our first model.</p><h3>3. Training the first model</h3><p>Let’s build our first model, using <a href="http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html">logistic regression</a> implementation from Sklearn with default parameters. We will use the first 90% of the data for training (the training data set is sorted by time), and the remaining 10% for validation. Let&#39;s write a simple function that returns the quality of the model, and then train our first classifier:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7d334213b12d6cb12242a101ad47ae24/href">https://medium.com/media/7d334213b12d6cb12242a101ad47ae24/href</a></iframe><pre>0.9198622553850315<br>CPU times: user 138 ms, sys: 77.1 ms, total: 216 ms<br>Wall time: 2.74 s</pre><p>The first model demonstrated the quality of approximately 0.92 ROC AUC on the validation set. Let’s take it as the first baseline and a starting point. To make a prediction on the test set, <em>we need to train the model again on the entire training dataset</em> (until this moment, our model used only part of the data for training), which will increase its generalizing ability:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5db46dc282c3e9c157705007c5dca810/href">https://medium.com/media/5db46dc282c3e9c157705007c5dca810/href</a></iframe><p>If you follow these steps and upload the answer to the competition <a href="https://inclass.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking">page</a>, then you should get the quality of ROC AUC = 0.91707 on the public leaderboard.</p><h3>Assignment #4</h3><p>Full versions of assignments are announced each week in a new run of the course (October 1, 2018). Meanwhile, you can practice with a demo version: <a href="https://www.kaggle.com/kashnitsky/assignment-4-linear-regression-as-optimization">Kaggle Kernel</a>, <a href="https://mlcourse.ai/notebooks/blob/master/jupyter_english/assignments_demo/assignment04_linreg_optimization.ipynb?flush_cache=true">nbviewer</a>.</p><h3>Useful resources</h3><ul><li>A nice and concise overview of linear models is given in the book “Deep Learning” (I. Goodfellow, Y. Bengio, and A. Courville).</li><li>Linear models are covered practically in every ML book. We recommend “Pattern Recognition and Machine Learning” (C. Bishop) and “Machine Learning: A Probabilistic Perspective” (K. Murphy).</li><li>If you prefer a thorough overview of linear model from a statistician’s viewpoint, then look at “The elements of statistical learning” (T. Hastie, R. Tibshirani, and J. Friedman).</li><li>The book “Machine Learning in Action” (P. Harrington) will walk you through implementations of classic ML algorithms in pure Python.</li><li><a href="http://scikit-learn.org/stable/documentation.html">Scikit-learn</a> library. These guys work hard on writing really clear documentation.</li><li>Scipy 2017 <a href="https://github.com/amueller/scipy-2017-sklearn">scikit-learn tutorial</a> by Alex Gramfort and Andreas Mueller.</li><li>One more <a href="https://github.com/diefimov/MTH594_MachineLearning">ML course</a> with very good materials.</li><li><a href="https://github.com/rushter/MLAlgorithms">Implementations</a> of many ML algorithms. Search for linear regression and logistic regression.</li><li>And many others, feel free to share in comments.</li></ul><p><em>Author: </em><a href="https://www.linkedin.com/in/festline/"><em>Yury Kashnitskiy</em></a><em>. Translated and edited by Yuriy Isakov, </em><a href="https://www.linkedin.com/in/christinabutsko/"><em>Christina Butsko</em></a><em>, </em><a href="https://www.linkedin.com/in/nersesbagiyan/"><em>Nerses Bagiyan</em></a><em>, </em><a href="https://www.linkedin.com/in/yuliya-klimushina-7168a9139"><em>Yulia Klimushina</em></a><em>, </em><a href="https://www.linkedin.com/in/anastasiamanokhina/"><em>Anastasia Manokhina</em></a><em>, </em><a href="https://www.linkedin.com/in/eugene-mashkin-88490883/"><em>Evgeniy Mashkin</em></a><em>, </em><a href="https://www.linkedin.com/in/egor-polusmak/"><em>Egor Polusmak</em></a><em>, and </em><a href="https://www.linkedin.com/in/yuanyuanpao/"><em>Yuanyuan Pao</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=44a41b9b5220" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-4-linear-classification-and-regression-44a41b9b5220">Open Machine Learning Course. Topic 4. Linear Classification and Regression</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 3. Classification, Decision Trees and k Nearest Neighbors]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-3-classification-decision-trees-and-k-nearest-neighbors-8613c6b6d2cd?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/8613c6b6d2cd</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[education]]></category>
            <category><![CDATA[scikit-learn]]></category>
            <category><![CDATA[classification]]></category>
            <category><![CDATA[decision-tree]]></category>
            <dc:creator><![CDATA[Yury Kashnitsky]]></dc:creator>
            <pubDate>Mon, 19 Feb 2018 09:45:26 GMT</pubDate>
            <atom:updated>2018-09-19T08:59:27.200Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5Tt3NDpNobfU_YeKglBRyQ.jpeg" /><figcaption>A fractal tree. <a href="https://alvenka.deviantart.com/art/fractal-tree-34-366989431">Source</a></figcaption></figure><p>Hi! This is the third article in our series. Today we finally reach machine learning. This is going to be exciting!</p><h3>Article outline</h3><ol><li>Introduction</li><li>Decision Tree</li></ol><ul><li>How to Build a Decision Tree</li><li>Tree-building Algorithm</li><li>Other Quality Criteria for Splits in Classification Problems</li><li>How a Decision Tree Works with Numerical Features</li><li>Crucial Tree Parameters</li><li>Class DecisionTreeClassifier in Scikit-learn</li><li>Decision Tree in a Regression Problem</li></ul><p>3. Nearest Neighbors Method</p><ul><li>Nearest Neighbors Method in Real Applications</li><li>Class KNeighborsClassifier in Scikit-learn</li></ul><p>4. Choosing Model Parameters and Cross-Validation</p><p>5. Application Examples and Complex Cases</p><ul><li>Decision trees and nearest neighbors method in a customer churn prediction task</li><li>Complex Case for Decision Trees</li><li>Decision Trees and k-NN in a Task of MNIST Handwritten Digits Recognition</li><li>Complex Case for the Nearest Neighbors Method</li></ul><p>6. Pros and Cons of Decision Trees and the Nearest Neighbors Method</p><p>7. Assignment #3</p><p>8. Useful resources</p><p><strong>The following material is better viewed as a </strong><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic03_decision_trees_kNN/topic3_decision_trees_kNN.ipynb?flush_cache=true"><strong>Jupyter notebook</strong></a><strong> and can be reproduced locally with Jupyter if you clone the </strong><a href="https://github.com/Yorko/mlcourse_open"><strong>course repository</strong></a><strong>.</strong></p><h3>1. Introduction</h3><p>Before we dive into the material for this week’s article, let’s talk about the kind of problem that we are going to solve and its place in the exciting field of machine learning. T. Mitchell’s book <em>Machine Learning</em> (1997) gives a classic, general definition of machine learning as follows:</p><blockquote>A computer program is said to learn from experience <em>E</em> with respect to some class of tasks <em>T</em> and performance measure <em>P</em>, if its performance at tasks in <em>T</em>, as measured by <em>P</em>, improves with experience <em>E</em>.</blockquote><p>In the various problem settings <em>T</em>, <em>P</em>, and <em>E</em> can refer to completely different things. Some of the most popular <strong>tasks <em>T</em> in machine learning</strong> are the following:</p><ul><li>classification of an instance to one of the categories based on its features;</li><li>regression — prediction of a numerical target feature based on other features of an instance;</li><li>clustering — identifying partitions of instances based on the features of these instances so that the members within the groups are more similar to each other than those in the other groups;</li><li>anomaly detection — search for instances that are “greatly dissimilar” to the rest of the sample or to some group of instances;</li><li>and so many more.</li></ul><p>A good overview is provided in the “Machine Learning basics” chapter of <a href="http://www.deeplearningbook.org/">“Deep Learning”</a> (by Ian Goodfellow, Yoshua Bengio, Aaron Courville, 2016).</p><p><strong>Experience <em>E</em></strong> refers to data (we can’t go anywhere without it). Machine learning algorithms can be divided into those that are trained in <em>supervised</em> or <em>unsupervised</em> manner. In unsupervised learning tasks, one has a <em>set</em> consisting of <em>instances</em> described by a set of <em>features</em>. In supervised learning problems, there’s also a <em>target variable</em>, which is what we would like to be able to predict, known for each instance in a <em>training set</em>.</p><h4>Example</h4><p>Classification and regression are supervised learning problems. For example, as a credit institution, we may want to predict loan defaults based on the data accumulated about our clients. Here, the experience <em>E</em> is the available training data: a set of <em>instances</em> (clients), a collection of <em>features</em> (such as age, salary, type of loan, past loan defaults, etc.) for each, and a <em>target variable</em> (whether they defaulted on the loan). This target variable is just a fact of loan default (1 or 0), so recall that this is a (binary) classification problem. If you were instead predicting <em>by how much time</em> the loan payment is overdue, this would become a regression problem.</p><p>Finally, the third term used in the definition of machine learning is a <strong>metric of the algorithm’s performance evaluation <em>P</em>.</strong> Such metrics differ for various problems and algorithms, and we’ll discuss them as we study new algorithms. For now, we’ll refer to a simple metric for classification algorithms, the proportion of correct answers — <em>accuracy</em> — on the test set.</p><p>Let’s take a look at two supervised learning problems: classification and regression.</p><h3>2. Decision Tree</h3><p>We begin our overview of classification and regression methods with one of the most popular ones — a decision tree. Decision trees are used in everyday life decisions, not just in machine learning. Flow diagrams are actually visual representations of decision trees. For example, Higher School of Economics publishes information diagrams to make the lives of its employees easier. Here is a snippet of instructions for publishing a paper on the Institution portal.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CIb1qGHEY-UptAWD.png" /></figure><p>In terms of machine learning, one can see it as a simple classifier that determines the appropriate form of publication (book, article, chapter of the book, preprint, publication in the “Higher School of Economics and the Media”) based on the content (book, pamphlet, paper), type of journal, original publication type (scientific journal, proceedings), etc.</p><p>A decision tree is often a generalization of the experts’ experience, a means of sharing knowledge of a particular process. For example, before the introduction of scalable machine learning algorithms, the credit scoring task in the banking sector was solved by experts. The decision to grant a loan was made on the basis of some intuitively (or empirically) derived rules that could be represented as a decision tree.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/440/1*gaIruEZ7kHJeLz0bhUo_GQ.png" /></figure><p>In our next case, we solve a binary classification problem (approve/deny a loan) on the grounds of “Age”, “Home-ownership”, “Income” and “Education”.</p><p>The decision tree as a machine learning algorithm is essentially the same thing as the diagram shown above; we incorporate a stream of logical rules of the form “feature <em>a</em> value is less than <em>x </em>and feature <em>b</em> value is less than <em>y</em> … =&gt; Category 1” into a tree-like data structure. The advantage of this algorithm is that they are easily interpretable. For example, using the above scheme, the bank can explain to the client why they were denied for a loan: e.g the client does not own a house and her income is less than 5,000.</p><p>As we’ll see later, many other models, although more accurate, do not have this property and can be regarded as more of a “black box” approach, where it is harder to interpret how the input data was transformed into the output. Due to this “understandability” and similarity to human decision-making (you can easily explain your model to your boss), decision trees have gained immense popularity. C4.5, a representative of this group of classification methods, is even the first in the list of 10 best data mining algorithms (“Top 10 Algorithms in Data Mining”, Knowledge and Information Systems, 2008. <a href="http://www.cs.uvm.edu/%7Eicdm/algorithms/10Algorithms-08.pdf">PDF</a>).</p><h3>How to Build a Decision Tree</h3><p>Earlier, we saw that the decision to grant a loan is made based on age, assets, income, and other variables. But what variable to look at first? Let’s discuss a simple example where all the variables are binary.</p><p>Recall the game of “20 Questions”, which is often referenced when introducing decision trees. You’ve probably played this game — one person thinks of a celebrity while the other tries to guess by asking only “Yes” or “No” questions. What question will the guesser ask first? Of course, they will ask the one that narrows down the number of the remaining options the most. Asking “Is it Angelina Jolie?” would, in the case of a negative response, leave all but one celebrity in the realm of possibility. In contrast, asking “Is the celebrity a woman?” would reduce the possibilities to roughly half. That is to say, the “gender” feature separates the celebrity dataset much better than other features like “Angelina Jolie”, “Spanish”, or “loves football.” This reasoning corresponds to the concept of information gain based on entropy.</p><h4>Entropy</h4><p>Shannon’s entropy is defined for a system with N possible states as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/353/1*cf-lyJLXXEDSK2H7GN8ncw.png" /></figure><p>where <em>Pi </em>is the probability of finding the system in the <em>i</em>-th state. This is a very important concept used in physics, information theory, and other areas. Entropy can be described as the degree of chaos in the system. The higher the entropy, the less ordered the system and vice versa. This will help us formalize “effective data splitting”, which we alluded to in the context of “20 Questions”.</p><h4>Toy Example</h4><p>To illustrate how entropy can help us identify good features for building a decision tree, let’s look at a toy example. We will predict the color of the ball based on its position.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/606/0*jW6I6V_I-Nn725It.png" /><figcaption><a href="https://habrahabr.ru/post/171759/">Source</a> (in Russian)</figcaption></figure><p>There are 9 blue balls and 11 yellow balls. If we randomly pull out a ball, then it will be blue with probability p1 = 9/20 and yellow with probability p2 = 11/20, which gives us an entropy S0 = -9/20 log2(9/20) - 11/20 log2(11/20) ≈ 1. This value by itself may not tell us much, but let’s see how the value changes if we were to break the balls into two groups: with the position less than or equal to 12 and greater than 12.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/717/0*JSFOJNkdOHNhSjdo.png" /><figcaption><a href="https://habrahabr.ru/post/171759/">Source</a> (in Russian)</figcaption></figure><p>The left group has 13 balls, 8 blue and 5 yellow. The entropy of this group is S1 = -5/13 log2(5/13) - 8/13 log2(8/13) ≈ 0.96. The right group has 7 balls, 1 blue and 6 yellow. The entropy of the right group is S2 = -1/7 log2(1/7) - 6/7 log2(6/7) ≈ 0.6. As you can see, entropy has decreased in both groups, more so in the right group. Since entropy is, in fact, the degree of chaos (or uncertainty) in the system, the reduction in entropy is called information gain. Formally, the information gain (IG) for a split based on the variable <em>Q</em>(in this example it’s a variable “<em>x ≤ 12</em>”) is defined as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/429/1*ewOFVFQEXRwamSPBwY2QGw.png" /></figure><p>where <em>q </em>is the number of groups after the split, <em>Ni</em> is number of objects from the sample in which variable <em>Q</em> is equal to the <em>i</em>-th value. In our example, our split yielded two groups (q = 2), one with 13 elements (N1 = 13), the other with 7 (N2 = 7). Therefore, we can compute the information gain as</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/703/1*wNF6UPLh_H-J0u70tlQU1g.png" /></figure><p>It turns out that dividing the balls into two groups by splitting on “coordinate is less than or equal to 12” gave us a more ordered system. Let’s continue to divide them into groups until the balls in each group are all of the same color.</p><p>For the right group, we can easily see that we only need one extra partition using “coordinate less than or equal to 18”. But, for the left group, we need three more. Note that the entropy of a group where all of the balls are the same color is equal to 0 (log2(1) = 0).</p><p>We have successfully constructed a decision tree that predicts ball color based on its position. This decision tree may not work well if we add any balls because it has perfectly fit to the training set (initial 20 balls). If we wanted to do well in that case, a tree with fewer “questions” or splits would be more accurate, even if it does not perfectly fit the training set. We will discuss the problem of overfitting later.</p><h3>Tree-building Algorithm</h3><p>We can make sure that the tree built in the previous example is optimal: it took only 5 “questions” (conditioned on the variable <em>x</em>) to perfectly fit a decision tree to the training set. Under other split conditions, the resulting tree would be deeper, i.e. take more “questions” to reach an answer.</p><p>At the heart of the popular algorithms for decision tree construction, such as ID3 or C4.5, lies the principle of greedy maximization of information gain: at each step, the algorithm chooses the variable that gives the greatest information gain upon splitting. Then the procedure is repeated recursively until the entropy is zero (or some small value to account for overfitting). Different algorithms use different heuristics for “early stopping” or “cut-off” to avoid constructing an overfitted tree.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/718/1*cKuUJ3ARs8tTk15Of_4btg.png" /></figure><h3>Other Quality Criteria for Splits in Classification Problems</h3><p>We discussed how entropy allows us to formalize partitions in a tree. But this is only one heuristic; there exist others.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/297/1*tQO9YrY-ntGAjM_RzW-pkA.png" /><figcaption>Gini uncertainty (Gini impurity)</figcaption></figure><p>Maximizing this criterion can be interpreted as the maximization of the number of pairs of objects of the same class that are in the same subtree (not to be confused with the Gini index).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/271/1*WOB8ZXlkfv-DLHvzaZl88A.png" /><figcaption>Misclassification error</figcaption></figure><p>In practice, misclassification error is almost never used, and Gini uncertainty and information gain work similarly.</p><p>For binary classification, entropy and Gini uncertainty take the following form:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/738/1*ijHoiJ08hV0KckUbIMERcw.png" /></figure><p>where (<em>p+</em> is the probability of an object having a label +).</p><p>If we plot these two functions against the argument <em>p+</em>, we will see that the entropy plot is very close to the plot of Gini uncertainty, doubled. Therefore, in practice, these two criteria are almost identical.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GC23qi3fl2hrWW4eUComgw.png" /></figure><h4>Example</h4><p>Let’s consider fitting a decision tree to some synthetic data. We will generate samples from two classes, both normal distributions but with different means.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2ef293179efea50d277703e91e4768df/href">https://medium.com/media/2ef293179efea50d277703e91e4768df/href</a></iframe><p>Let’s plot the data. Informally, the classification problem in this case is to build some “good” boundary separating the two classes (the red dots from the yellow). Machine learning for this case boils down to choosing a good separating border. A straight line will be too simple while some complex curve snaking by each red dot will be too complex and will lead us to making mistakes on new samples. Intuitively, some smooth boundary, or, at least, a straight line or a hyperplane, would work well on new data.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fc55ac1553e94cc62ff5b94fc3b0b6c2/href">https://medium.com/media/fc55ac1553e94cc62ff5b94fc3b0b6c2/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*77R2NOZymudraI89GH9LuA.png" /></figure><p>Let’s try to separate these two classes by training an Sklearn decision tree. We will use max_depth parameter that limits the depth of the tree. Let&#39;s visualize the resulting separating boundary.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f274a20bcb6b98c87a68863e5dd4c043/href">https://medium.com/media/f274a20bcb6b98c87a68863e5dd4c043/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4t4IrcQdKplQsAE4SBduXg.png" /></figure><p>And how does the tree itself look? We see that the tree “cuts” the space into 8 rectangles, i.e. the tree has 8 leaves. Within each rectangle, the tree will make the prediction according to the majority label of the objects inside it.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/664fe19fd7f66fcee668fa5042fea92f/href">https://medium.com/media/664fe19fd7f66fcee668fa5042fea92f/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aNhu3p8AkkGs3uJlKdLUjg.png" /></figure><h4>How can we “read” such a tree?</h4><p>In the beginning, there were 200 samples (instances), 100 of each class. The entropy of the initial state was maximal, S=1. Then, the first partition of the samples into 2 groups was made by comparing the value of <em>x2</em> with 1.211 (find this part of the border in the picture above). With that, the entropy of both left and right groups decreased. The process continues up to depth 3. In this visualization, the more samples of the first class, the darker the orange color of the vertex; the more samples of the second class, the darker the blue. At the beginning, the number of samples from two classes is equal, so the root node of the tree is white.</p><h3>How a Decision Tree Works with Numerical Features</h3><p>Suppose we have a numeric feature “Age” that has a lot of unique values. A decision tree will look for the best (according to some criterion of information gain) split by checking binary attributes such as “Age &lt;17”, “Age &lt; 22.87”, and so on. But what if the age range is large? Or what if another quantitative variable, “salary”, can also be “cut” in many ways? There will be too many binary attributes to select from at each step during tree construction. To resolve this problem, heuristics are usually used to limit the number of thresholds to which we compare the quantitative variable.</p><p>Let’s consider an example. Suppose we have the following dataset:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dfb2658bcc651357a93115d2ac6bf5ea/href">https://medium.com/media/dfb2658bcc651357a93115d2ac6bf5ea/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/220/1*d17quX4zhAmYEe_JJ_IN4g.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d9600bc0df919950bed4057878bb0409/href">https://medium.com/media/d9600bc0df919950bed4057878bb0409/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/499/1*a03Xr_5-wfKDzWoqHUiyyg.png" /></figure><p>We see that the tree used the following 5 values to evaluate by age: 43.5, 19, 22.5, 30, and 32 years. If you look closely, these are exactly the mean values between the ages at which the target class “switches” from 1 to 0 or 0 to 1. To illustrate further, 43.5 is the average of 38 and 49 years; a 38-year-old customer failed to return the loan whereas the 49-year-old did. The tree looks for the values at which the target class switches its value as a threshold for “cutting” a quantitative variable.</p><p>Given this information, why do you think it makes no sense here to consider a feature like “Age &lt;17.5”?</p><p>Let’s consider a more complex example by adding the “Salary” variable (in the thousands of dollars per year).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/abc0df2a49847ff6bd541717eee0ba0d/href">https://medium.com/media/abc0df2a49847ff6bd541717eee0ba0d/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/292/1*6VJBLqoPTy07sJLQa3iNrQ.png" /></figure><p>If we sort by age, the target class ( “loan default”) switches (from 1 to 0 or vice versa) 5 times. And if we sort by salary, it switches 7 times. How will the tree choose features now? Let’s see.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ebf0b04c2d2276e599cedb424e119acd/href">https://medium.com/media/ebf0b04c2d2276e599cedb424e119acd/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/505/1*pZbJ-J1Q_UW-AH1KrhbiBA.png" /></figure><p>We see that the tree partitioned by both salary and age. Moreover, the thresholds for feature comparisons are 43.5 and 22.5 years of age and 95k and 30.5k per year. Again, we see that 95 is the average between 88 and 102; the individual with a salary of 88k proved to be “bad” while the one with 102k was “good”. The same goes for 30.5k. That is, only a few values for comparisons by age and salary were searched. Why did the tree choose these features? Because they gave better partitioning (according to Gini uncertainty).</p><p><strong>Conclusion</strong>: the simplest heuristics for handling numeric features in a decision tree is to sort its values in ascending order and check only those thresholds where the value of the target variable changes.</p><p>Furthermore, when there are a lot of numeric features in a dataset, each with a lot of unique values, only the top-N of the thresholds described above are selected, i.e. only use the top-N that give maximum gain. The process is to construct a tree of depth 1, compute the entropy (or Gini uncertainty), and select the best thresholds for comparison.</p><p>To illustrate, if we split by “Salary ≤ 34.5”, the left subgroup will have the entropy of 0 (all clients are “bad”), and the right one will have the entropy of 0.954 (3 “bad” and 5 “good”, you can check this yourself as it will be part of the assignment). The information gain is roughly 0.3. If we split by “Salary ≤ 95”, the left subgroup will have an entropy of 0.97 (6 “bad” and 4 “good”), and the right one will have an entropy of 0 (a group containing only one object). The information gain is about 0.11. If we calculate information gain for each partition in that manner, we can select the thresholds for comparison of each numeric feature before the construction of a large tree (using all features).</p><p>More examples of numeric feature discretization can be found in posts like <a href="http://kevinmeurer.com/a-simple-guide-to-entropy-based-discretization/">this</a> or <a href="http://clear-lines.com/blog/post/Discretizing-a-continuous-variable-using-Entropy.aspx">this</a>. One of the most prominent scientific papers on this subject is “On the handling of continuous-valued attributes in decision tree generation” (UM Fayyad. KB Irani, “Machine Learning”, 1992).</p><h3>Crucial Tree Parameters</h3><p>Technically, you can build a decision tree until each leaf has exactly one instance, but this is not common in practice when building a single tree because it will be <em>overfitted</em>, or too tuned to the training set, and will not predict labels for new data well. At the bottom of the tree, at some great depth, there will be partitions on less important features (e.g. whether a client came from Leeds or New York). We can exaggerate this story further and find that all four clients who came to the bank for a loan in green trousers did not return the loan. Even if that were true in training, we do not want our classification model to generate such specific rules.</p><p>There are two exceptions where the trees are built to the maximum depth:</p><ul><li>Random Forest (a group of trees) averages the responses from individual trees that are built to the maximum depth (we will talk later on why you should do this)</li><li><em>Pruning</em> trees. In this approach, the tree is first constructed to the maximum depth. Then, from the bottom up, some nodes of the tree are removed by comparing the quality of the tree with and without that partition (comparison is performed using <em>cross-validation</em>, more on this below).</li></ul><p>The picture below is an example of a dividing border built in an overfitted tree.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/590/0*Lg34OEoe_VlX2-1n.png" /></figure><p>The most common ways to deal with overfitting in decision trees are as follows:</p><ul><li>artificial limitation of the depth or a minimum number of samples in the leaves: the construction of a tree just stops at some point;</li><li>pruning the tree.</li></ul><h3>Class DecisionTreeClassifier in Scikit-learn</h3><p>The main parameters of the <a href="http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html">sklearn.tree.DecisionTreeClassifier</a> class are:</p><ul><li>max_depth – the maximum depth of the tree;</li><li>max_features – the maximum number of features with which to search for the best partition (this is necessary with a large number of features because it would be &quot;expensive&quot; to search for partitions for <em>all</em> features);</li><li>min_samples_leaf – the minimum number of samples in a leaf. This parameter prevents creating trees where any leaf would have only a few members.</li></ul><p>The parameters of the tree need to be set depending on input data, and it is usually done by means of <em>cross-validation</em>, more on this below.</p><h3>Decision Tree in a Regression Problem</h3><p>When predicting a numeric variable, the idea of a tree construction remains the same, but the quality criteria changes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/455/1*iiqWU5CR4F1F_8aQFaOpvg.png" /><figcaption>Variance around the mean</figcaption></figure><p>where <em>n</em> is the number of samples in a leaf, <em>Yi </em>is the value of the target variable. Simply put, by minimizing the variance around the mean, we look for features that divide the training set in such a way that the values of the target feature in each leaf are roughly equal.</p><h4>Example</h4><p>Let’s generate some data distributed by the function</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/257/1*JlU7gNyjvAJDLc9hbLJVKg.png" /></figure><p>with some noise. Then we will train a tree on it and show what predictions it makes.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fa9377033bbe22e1bcd9e2f6439d8b5a/href">https://medium.com/media/fa9377033bbe22e1bcd9e2f6439d8b5a/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/604/1*-_dzWXtxS6XpmZemkKacnw.png" /></figure><p>We see that the decision tree approximates the data with a piecewise constant function.</p><h3>3. Nearest Neighbors Method</h3><p><em>The nearest neighbors method</em> (k-Nearest Neighbors, or k-NN) is another very popular classification method that is also sometimes used in regression problems. This, like decision trees, is one of the most comprehensible approaches to classification. The underlying intuition is that you look like your neighbors. More formally, the method follows the compactness hypothesis: if the distance between the examples is measured well enough, then similar examples are much more likely to belong to the same class.</p><p>According to the nearest neighbors method, the green ball would be classified as “blue” rather than “red”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/404/0*TMLaS4woI6xo6RKs.png" /></figure><p>For another example, if you do not know how to tag a Bluetooth-headset on an online listing, you can find 5 similar headsets, and, if 4 of them are tagged as “accessories” and only 1 as “Technology”, then you will also label it under “accessories”.</p><p>To classify each sample from the test set, one needs to perform the following operations in order:</p><ol><li>Calculate the distance to each of the samples in the training set.</li><li>Select <em>k</em> samples from the training set with the minimal distance to them.</li><li>The class of the test sample will be the most frequent class among those <em>k</em> nearest neighbors.</li></ol><p>The method adapts quite easily for the regression problem: on step 3, it returns not the class, but the number — a mean (or median) of the target variable among neighbors.</p><p>A notable feature of this approach is its laziness — calculations are only done during the prediction phase, when a test sample needs to be classified. No model is constructed from the training examples beforehand. In contrast, recall that for decision trees in the first half of this article the tree is constructed based on the training set, and the classification of test cases occurs relatively quickly by traversing through the tree.</p><p>Nearest neighbors is a well-studied approach. There exist many important theorems claiming that, on “endless” datasets, it is the optimal method of classification. The authors of the classic book “The Elements of Statistical Learning” consider k-NN to be a theoretically ideal algorithm which usage is only limited by computation power and the <a href="https://en.wikipedia.org/wiki/Curse_of_dimensionality">curse of dimensionality</a>.</p><h3>Nearest Neighbors Method in Real Applications</h3><ul><li>k-NN can serve as a good starting point (baseline) in some cases;</li><li>In Kaggle competitions, k-NN is often used for the construction of meta-features (i.e. k-NN predictions as input to other models) or for stacking/blending;</li><li>The nearest neighbors method extends to other tasks like recommendation systems. The initial decision could be a recommendation of a product (or service) that is popular among the <em>closest neighbors</em> of the person for whom we want to make a recommendation;</li><li>In practice, on large datasets, approximate methods of search are often used for nearest neighbors. There is a number of open source libraries that implement such algorithms; check out Spotify’s library <a href="https://github.com/spotify/annoy">Annoy</a>.</li></ul><p>The quality of classification/regression with k-NN depends on several parameters:</p><ul><li>The number of neighbors <em>k</em>.</li><li>The distance measure between samples (common ones include Hamming, Euclidean, cosine, and Minkowski distances). Note that most of these metrics require data to be scaled. Simply speaking, we do not want the “salary” feature, which is on the order of thousands, to affect the distance more than “age”, which is generally less than 100.</li><li>Weights of neighbors (each neighbor may contribute different weights; for example, the further the sample, the lower the weight).</li></ul><h3>Class KNeighborsClassifier in Scikit-learn</h3><p>The main parameters of the class sklearn.neighbors.KNeighborsClassifier are:</p><ul><li>weights: uniform (all weights are equal), distance (the weight is inversely proportional to the distance from the test sample), or any other user-defined function;</li><li>algorithm (optional): brute, ball_tree, KD_tree, or auto. In the first case, the nearest neighbors for each test case are computed by a grid search over the training set. In the second and third cases, the distances between the examples are stored in a tree to accelerate finding nearest neighbors. If you set this parameter to auto, the right way to find the neighbors will be automatically chosen based on the training set.</li><li>leaf_size (optional): threshold for switching to grid search if the algorithm for finding neighbors is BallTree or KDTree;</li><li>metric: minkowski, manhattan, euclidean, chebyshev, or other.</li></ul><h3>4. Choosing Model Parameters and Cross-Validation</h3><p>The main task of learning algorithms is to be able to <em>generalize</em> to unseen data. Since we cannot immediately check the model performance on new, incoming data (because we do not know the true values of the target variable yet), it is necessary to sacrifice a small portion of the data to check the quality of the model on it.</p><p>This is often done in one of two ways:</p><ul><li>setting aside a part of the dataset (<em>held-out/hold-out set</em>). We reserve a fraction of the training set (typically from 20% to 40%), train the model on the remaining data (60–80% of the original set), and compute performance metrics for the model (e.g accuracy) on the hold-out set.</li><li><em>cross-validation</em>. The most frequent case here is <em>k-fold cross-validation</em>.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*iGVu2OmIL1WUSjZF.png" /></figure><p>In k-fold cross-validation, the model is trained <em>K</em> times on different (<em>K-1</em>) subsets of the original dataset (in white) and checked on the remaining subset (each time a different one, shown above in orange). We obtain <em>K</em> model quality assessments that are usually averaged to give an overall average quality of classification/regression.</p><p>Cross-validation provides a better assessment of the model quality on new data compared to the hold-out set approach. However, cross-validation is computationally expensive when you have a lot of data.</p><p>Cross-validation is a very important technique in machine learning and can also be applied in statistics and econometrics. It helps with hyperparameter tuning, model comparison, feature evaluation, etc. More details can be found <a href="https://sebastianraschka.com/blog/2016/model-evaluation-selection-part1.html">here</a> (blog post by Sebastian Raschka) or in any classic textbook on machine (statistical) learning.</p><h3>5. Application Examples and Complex Cases</h3><h3>Decision trees and nearest neighbors method in a customer churn prediction task</h3><p>Let’s read data into a DataFrame and preprocess it. Store <em>State</em> in a separate Series object for now and remove it from the dataframe. We will train the first model without the <em>State</em> feature, and then we will see if it helps.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d2df271a0611166325e3f2d13e7da48e/href">https://medium.com/media/d2df271a0611166325e3f2d13e7da48e/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/988/1*_ayZZheYD8jFveMgXZ_7fA.png" /></figure><p>Let’s allocate 70% of the set for training (X_train, y_train) and 30% for the hold-out set (X_holdout, y_holdout). The hold-out set will not be involved in tuning the parameters of the models. We&#39;ll use it at the end, after tuning, to assess the quality of the resulting model. Let&#39;s train 2 models: a decision tree and k-NN. We do not know what parameters are good, so we will assume some random ones: a tree depth of 5 and the number of nearest neighbors equal 10.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c50323ecd9e9d8ead17911341cb3b122/href">https://medium.com/media/c50323ecd9e9d8ead17911341cb3b122/href</a></iframe><p>Let’s assess prediction quality on our hold-out set with a simple metric — the proportion of correct answers (accuracy). The decision tree did better — percentage of correct answers is about 94% (decision tree) versus 88% (k-NN). Note that this performance is achieved by using random parameters.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/12e78c34ff52832de27915dfe278b686/href">https://medium.com/media/12e78c34ff52832de27915dfe278b686/href</a></iframe><p>Now, let’s identify the parameters for the tree using cross-validation. We’ll tune the maximum depth and the maximum number of features used at each split. Here is the essence of how the GridSearchCV works: for each unique pair of values of max_depth and max_features, compute model performance with 5-fold cross-validation, and then select the best combination of parameters.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f203d8b929e433a538f1e44a0447e2ae/href">https://medium.com/media/f203d8b929e433a538f1e44a0447e2ae/href</a></iframe><p>Let’s list the best parameters and the corresponding mean accuracy from cross-validation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4d88a39f1ed85538a4b253c7fba32e6f/href">https://medium.com/media/4d88a39f1ed85538a4b253c7fba32e6f/href</a></iframe><p>Let’s draw the resulting tree. Due to the fact that it is not entirely a toy example (its maximum depth is 6), the picture is not that small, but you can “walk” over the tree if you locally open the corresponding picture downloaded from the course repo.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7b9b552e1176b50bc0bc267d9a5cdd79/href">https://medium.com/media/7b9b552e1176b50bc0bc267d9a5cdd79/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yBZp1DXgtxBHsi8UxeC6Cg.png" /></figure><p>Now, let’s tune the number of neighbors <em>k</em> for k-NN:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b44bdf193962dfd5166d8014e95e895d/href">https://medium.com/media/b44bdf193962dfd5166d8014e95e895d/href</a></iframe><p>Here, the tree proved to be better than the nearest neighbors algorithm: 94.2%/94.6% accuracy for cross-validation and hold-out respectively. Decision trees perform very well, and even random forest (let’s think of it for now as a bunch of trees that work better together) in this example cannot achieve better performance (95.1%/95.3%) despite being trained for much longer.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8623f3bc35337b021f651c2b6f380456/href">https://medium.com/media/8623f3bc35337b021f651c2b6f380456/href</a></iframe><h3>Complex Case for Decision Trees</h3><p>To continue the discussion of the pros and cons of the methods in question, let’s consider a simple classification task, where a tree would perform well but does it in an “overly complicated” manner. Let’s create a set of points on a plane (2 features), each point will be one of two classes (+1 for red, or -1 for yellow). If you look at it as a classification problem, it seems very simple: the classes are separated by a line.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eb61c66d2367a4a7732b668abfce8ca7/href">https://medium.com/media/eb61c66d2367a4a7732b668abfce8ca7/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/590/1*oKfEmX5DBMev8e_Hqnv53g.png" /></figure><p>However, the border that the decision tree builds is too complicated; plus the tree itself is very deep. Also, imagine how badly the tree will generalize to the space beyond the 30 x 30 squares that frame the training set.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7ae048fab32bcc36d0de556428ba04e1/href">https://medium.com/media/7ae048fab32bcc36d0de556428ba04e1/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_OqTKeKHCUx0J38T7gGTYw.png" /></figure><p>We got this overly complex construction, although the solution is just a straight line <em>x1 = x2</em>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1af7e72326579eeeec067b07233bc5d6/href">https://medium.com/media/1af7e72326579eeeec067b07233bc5d6/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oDkLhpY3kfa87UsjIp51ZA.png" /></figure><p>The method of one nearest neighbor does better than the tree but is still not as good as a linear classifier (our <a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-4-linear-classification-and-regression-44a41b9b5220">next topic</a>).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6b4d3434376c2d263163367449a149e3/href">https://medium.com/media/6b4d3434376c2d263163367449a149e3/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/596/1*CSUaToczckWZGPf7wsKAKA.png" /></figure><h3>Decision Trees and k-NN in a Task of MNIST Handwritten Digits Recognition</h3><p>Now let’s have a look at how these 2 algorithms perform on a real-world task. We will use the sklearn built-in dataset on handwritten digits. This task is an example where k-NN works surprisingly well.</p><p>Pictures here are 8x8 matrices (intensity of white color for each pixel). Then each such matrix is ​​”unfolded” into a vector of length 64, and we obtain a feature description of an object.</p><p>Let’s draw some handwritten digits. We see that they are distinguishable.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eeb9bf710715c6ef43864db717033080/href">https://medium.com/media/eeb9bf710715c6ef43864db717033080/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/919/1*u8zadz3PKNo3fOWFJm_vDA.png" /></figure><p>Next, let’s do the same experiment as in the previous task, but, this time, let’s change the ranges for tunable parameters.</p><p>Let’s select 70% of the dataset for training (X_train, y_train) and 30% for holdout (X_holdout, y_holdout). The holdout set will not participate in model parameters tuning; we will use it at the end to check the quality of the resulting model.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d18f2fa52ae89eb4182722b57dde7a19/href">https://medium.com/media/d18f2fa52ae89eb4182722b57dde7a19/href</a></iframe><p>Let’s train a decision tree and k-NN with our random parameters and make predictions on our holdout set. We can see that k-NN did much better, but note that this is with random parameters.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/af1edbb0c7f4af8a297f4b289f55d815/href">https://medium.com/media/af1edbb0c7f4af8a297f4b289f55d815/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2aed2efa0ee6555018d2ccecd4f3c680/href">https://medium.com/media/2aed2efa0ee6555018d2ccecd4f3c680/href</a></iframe><p>Now let’s tune our model parameters using cross-validation as before, but now we’ll take into account that we have more features than in the previous task: 64.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/85b385de90910523b1bb8c1969db5bed/href">https://medium.com/media/85b385de90910523b1bb8c1969db5bed/href</a></iframe><p>Let’s see the best parameters combination and the corresponding accuracy from cross-validation:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/36b64aa1c99ee8d214c83773a7964bff/href">https://medium.com/media/36b64aa1c99ee8d214c83773a7964bff/href</a></iframe><p>That has already passed 66% but not quite 97%. k-NN works better on this dataset. In the case of one nearest neighbor, we were able to reach 99% guesses on cross-validation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9bb1f93487ab77f352d87d17165392ee/href">https://medium.com/media/9bb1f93487ab77f352d87d17165392ee/href</a></iframe><p>Let’s train a random forest on the same dataset, it works better than k-NN on the majority of datasets. But we here have an exception.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/66ac4a6910d9a2ec12ce8721a974fb91/href">https://medium.com/media/66ac4a6910d9a2ec12ce8721a974fb91/href</a></iframe><p>You would be right to point out that we have not tuned any RandomForestClassifier parameters here. Even with tuning, the training accuracy doesn’t reach 98% as it did with one nearest neighbor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/288/1*9ZRg1FTetV7cMI_oNNcD_w.png" /><figcaption><em>CV and Holdout are average shares of the correct answers on cross-model validation and hold-out sample. DT stands for a decision tree, k-NN stands for k-nearest neighbors, RF stands for random forest</em></figcaption></figure><p>The <strong>conclusion</strong> of this experiment (and general advice): first check simple models on your data: decision tree and nearest neighbors (next time we will also add logistic regression to this list). It might be the case that these methods already work well enough.</p><h3>Complex Case for the Nearest Neighbors Method</h3><p>Let’s consider another simple example. In the classification problem, one of the features will just be proportional to the vector of responses, but this won’t help for the nearest neighbors method.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/559da4052b71b58187c316d9173cfde1/href">https://medium.com/media/559da4052b71b58187c316d9173cfde1/href</a></iframe><p>As always, we will look at the accuracy for cross-validation and the hold-out set. Let’s construct curves reflecting the dependence of these quantities on the n_neighbors parameter in the method of nearest neighbors. These curves are called validation curves.</p><p>One can see that k-NN with the Euclidean distance does not work well on the problem, even when you vary the number of nearest neighbors over a wide range.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/52ee260c895c98755c6cb3ef99d3a6ce/href">https://medium.com/media/52ee260c895c98755c6cb3ef99d3a6ce/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/598/1*_jt-Vi1_0IRsC0riZ1nb6g.png" /></figure><p>In contrast, the decision tree easily “detects” hidden dependencies in the data despite a restriction on the maximum depth.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6b09e4c1be2d25a3ad3ebcdabdbb502f/href">https://medium.com/media/6b09e4c1be2d25a3ad3ebcdabdbb502f/href</a></iframe><p>In the second example, the tree solved the problem perfectly while k-NN experienced difficulties. However, this is more of a disadvantage of using Euclidian distance than of the method. It did not allow us to reveal that one feature was much better than the others.</p><h3>6. Pros and Cons of Decision Trees and the Nearest Neighbors Method</h3><h4>Pros and cons of decision trees</h4><p>Pros:</p><ul><li>Generation of clear human-understandable classification rules, e.g. “if age &lt;25 and is interested in motorcycles, deny the loan”. This property is called interpretability of the model.</li><li>Decision trees can be easily visualized, i.e. both the model itself (the tree) and prediction for a certain test object (a path in the tree) can “be interpreted”.</li><li>Fast training and forecasting.</li><li>Small number of model parameters.</li><li>Supports both numerical and categorical features.</li></ul><p>Cons:</p><ul><li>The trees are very sensitive to the noise in input data; the whole model could change if the training set is slightly modified (e.g. remove a feature, add some objects). This impairs the interpretability of the model.</li><li>Separating border built by a decision tree has its limitations — it consists of hyperplanes perpendicular to one of the coordinate axes, which is inferior in quality to some other methods, in practice.</li><li>We need to avoid overfitting by pruning, setting a minimum number of samples in each leaf, or defining a maximum depth for the tree. Note that overfitting is an issue for all machine learning methods.</li><li>Instability. Small changes to the data can significantly change the decision tree. This problem is tackled with decision tree ensembles (discussed next time).</li><li>The optimal decision tree search problem is NP-complete. Some heuristics are used in practice such as greedy search for a feature with maximum information gain, but it does not guarantee finding the globally optimal tree.</li><li>Difficulties to support missing values in the data. Friedman estimated that it took about 50% of the code to support gaps in data in CART (an improved version of this algorithm is implemented in sklearn).</li><li>The model can only interpolate but not extrapolate (the same is true for random forests and tree boosting). That is, a decision tree makes constant prediction for the objects that lie beyond the bounding box set by the training set in the feature space. In our example with the yellow and blue balls, it would mean that the model gives the same predictions for all balls with positions &gt;19 or &lt;0.</li></ul><h4>Pros and cons of the nearest neighbors method</h4><p>Pros:</p><ul><li>Simple implementation.</li><li>Well studied.</li><li>Typically, the method is a good first solution not only for classification or regression, but also recommendations.</li><li>It can be adapted to a certain problem by choosing the right metrics or kernel (in a nutshell, the kernel may set the similarity operation for complex objects such as graphs while keeping the k-NN approach the same). By the way, <a href="https://www.kaggle.com/dyakonov">Alexander Dyakonov</a>, a former top-1 kaggler, loves the simplest k-NN but with the tuned object similarity metric.</li><li>Good interpretability. There are exceptions: if the number of neighbors is large, the interpretability deteriorates (“We did not give him a loan, because he is similar to the 350 clients, of which 70 are the bad, and that is 12% higher than the average for the dataset”).</li></ul><p>Cons:</p><ul><li>Method considered fast in comparison with compositions of algorithms, but the number of neighbors used for classification is usually large (100–150) in real life, in which case the algorithm will not operate as fast as a decision tree.</li><li>If a dataset has many variables, it is difficult to find the right weights and to determine which features are not important for classification/regression.</li><li>Dependency on the selected distance metric between the objects. Selecting the Euclidean distance by default is often unfounded. You can find a good solution by grid searching over parameters, but this becomes very time consuming for large datasets.</li><li>There are no theoretical ways to choose the number of neighbors — only grid search (though this is often true for all hyperparameters of all models). In the case of a small number of neighbors, the method is sensitive to outliers, that is, it is inclined to overfit.</li><li>As a rule, it does not work well when there are a lot of features due to the “curse of dimensionality”. Professor Pedro Domingos, a well-known member in the ML community, talks about it <a href="https://homes.cs.washington.edu/%7Epedrod/papers/cacm12.pdf">here</a> in his popular paper, “A Few Useful Things to Know about Machine Learning”; also “the curse of dimensionality” is described in the Deep Learning book in <a href="http://www.deeplearningbook.org/contents/ml.html">this chapter</a>.</li></ul><p>This is a lot of information, but, hopefully, this article will be a great reference for you for a long time :)</p><h3>7. Assignment #3</h3><p>Full versions of assignments are announced each week in a new run of the course (October 1, 2018). Meanwhile, you can practice with a demo version: <a href="https://www.kaggle.com/kashnitsky/assignment-3-decision-trees">Kaggle Kernel</a>, <a href="https://mlcourse.ai/notebooks/blob/master/jupyter_english/assignments_demo/assignment03_decision_trees.ipynb?flush_cache=true">nbviewer</a>.</p><h3>8. Useful resources</h3><ul><li>Decision trees and k Nearest Neighbors are covered practically in every ML book. We recommend “Pattern Recognition and Machine Learning” (C. Bishop) and “Machine Learning: A Probabilistic Perspective” (K. Murphy).</li><li>The book “Machine Learning in Action” (P. Harrington) will walk you through implementations of classic ML algorithms in pure Python.</li><li><a href="http://scikit-learn.org/stable/documentation.html">Scikit-learn</a> library. These guys work hard on writing really clear documentation.</li><li>Scipy 2017 <a href="https://github.com/amueller/scipy-2017-sklearn">scikit-learn tutorial</a> by Alex Gramfort and Andreas Mueller.</li><li>One more <a href="https://github.com/diefimov/MTH594_MachineLearning">ML course</a> with very good materials.</li><li><a href="https://github.com/rushter/MLAlgorithms">Implementations</a> of many ML algorithms. Good to search for decision trees and k-NN.</li><li>And many others, feel free to share in comments.</li></ul><p><em>Author: </em><a href="https://www.linkedin.com/in/festline/"><em>Yury Kashnitskiy</em></a><em>. Translated and edited by </em><a href="https://www.linkedin.com/in/christinabutsko/"><em>Christina Butsko</em></a><em>, Gleb Filatov, and </em><a href="https://www.linkedin.com/in/yuanyuanpao/"><em>Yuanyuan Pao</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8613c6b6d2cd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-3-classification-decision-trees-and-k-nearest-neighbors-8613c6b6d2cd">Open Machine Learning Course. Topic 3. Classification, Decision Trees and k Nearest Neighbors</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Machine Learning Course. Topic 2. Visual Data Analysis with Python]]></title>
            <link>https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-2-visual-data-analysis-in-python-846b989675cd?source=rss----b76595fb6cfc---4</link>
            <guid isPermaLink="false">https://medium.com/p/846b989675cd</guid>
            <category><![CDATA[visualization]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[education]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Egor Polusmak]]></dc:creator>
            <pubDate>Mon, 12 Feb 2018 10:39:38 GMT</pubDate>
            <atom:updated>2018-09-19T09:01:30.447Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mN1oy2M6X5pPeIkZXLTDeg.jpeg" /></figure><p>In the field of Machine Learning, <em>data visualization</em> is not just making fancy graphics for reports; it is used extensively in day-to-day work for all phases of a project.</p><p>To start with, visual exploration of data is the first thing one tends to do when dealing with a new task. We do preliminary checks and analysis using graphics and tables to summarize the data and leave out the less important details. It is much more convenient for us, humans, to grasp the main points this way than by reading many lines of raw data. It is amazing how much insight can be gained from seemingly simple charts created with available visualization tools.</p><p>Next, when we analyze the performance of a model or report results, we also often use charts and images. Sometimes, for interpreting a complex model, we need to project high-dimensional spaces onto more visually intelligible 2D or 3D figures.</p><p>All in all, visualization is a relatively fast way to learn something new about your data. Thus, it is vital to learn its most useful techniques and make them part of your everyday ML toolbox.</p><p>In this article, we are going to get hands-on experience with visual exploration of data using popular libraries such as pandas, matplotlib and seaborn.</p><h3>Article outline</h3><ol><li><strong>Dataset</strong></li><li><strong>Univariate</strong> <strong>visualization</strong>: quantitative and categorical distributions</li><li><strong>Multivariate visualization</strong>: interactions between variables</li><li><strong>Whole dataset</strong>: peeking into high-dimensional spaces</li><li><strong>Assignment #2</strong></li><li><strong>Useful resources</strong></li></ol><p><strong>The following material is better viewed as a </strong><a href="http://nbviewer.jupyter.org/github/Yorko/mlcourse_open/blob/master/jupyter_english/topic02_visual_data_analysis/topic2_visual_data_analysis.ipynb?flush_cache=true"><strong>Jupyter notebook</strong></a><strong> and can be reproduced locally with Jupyter if you clone the </strong><a href="https://github.com/Yorko/mlcourse_open"><strong>course repository</strong></a><strong>.</strong></p><h3>1. Dataset</h3><p>Before we get to the data, let’s initialize our environment:</p><pre>import numpy as np<br>import pandas as pd<br>pd.options.display.max_columns = 12</pre><pre><em># Disable warnings in Anaconda<br></em>import warnings<em><br></em>warnings.simplefilter(&#39;ignore&#39;)</pre><pre><em># We will display plots right inside Jupyter Notebook<br></em>%matplotlib inline<br>import matplotlib.pyplot as plt</pre><pre><em># We will use the Seaborn library<br></em>import seaborn as sns<br>sns.set()</pre><pre><em># Graphics in SVG format are more sharp and legible<br></em>%config InlineBackend.figure_format = &#39;svg&#39;</pre><pre><em># Increase the default plot size<br></em>from pylab import rcParams<br>rcParams[&#39;figure.figsize&#39;] = 5, 4</pre><p>In the first article, we looked at the data on customer churn for a telecom operator. We will load again that dataset into a DataFrame:</p><pre>df = pd.<strong>read_csv</strong>(&#39;../../data/telecom_churn.csv&#39;)</pre><p>To get acquainted with our data, let’s look at the first 5 entries using head():</p><pre>df.<strong>head</strong>()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CWgvuTxH0QcMkVWfD58d3A.png" /></figure><p>Here is the description of our features:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VU9HRDgbFebzp3m5VLserw.png" /></figure><p>The last data column, <strong>Churn</strong>, is our target variable. It is binary: <em>True</em> indicates that that the company eventually lost this customer, and <em>False</em> indicates that the customer was retained. Later, we will build models that predict this variable based on the remaining features. This is why we call it a <em>target</em>.</p><h3>2. Univariate visualization</h3><p><em>Univariate</em> analysis looks at one variable at a time. When we analyze a feature independently, we are usually mostly interested in the <em>distribution of its values</em> and ignore the other variables in the dataset.</p><p>Below, we will consider different statistical types of variables and the corresponding tools for their individual visual analysis.</p><h4>2.1 Quantitative features</h4><p><em>Quantitative features</em> take on ordered numerical values. Those values can be <em>discrete</em>, like integers, or <em>continuous</em>, like real numbers, and usually express a count or a measurement.</p><p><strong>Histograms and density plots</strong></p><p>The easiest way to take a look at the distribution of a numerical variable is to plot its <em>histogram</em> using the DataFrame&#39;s method <a href="https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.hist.html">hist()</a>.</p><pre>features = [&#39;Total day minutes&#39;, &#39;Total intl calls&#39;]<br>df[features].<strong>hist</strong>(figsize=(12, 4));</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uIK5ldeiKDZefR1WK1X7Dg.png" /></figure><p>A histogram groups values into <em>bins</em> of equal value range. The shape of the histogram may contain clues about the underlying distribution type: Gaussian, exponential etc. You can also spot any skewness in its shape when the distribution is nearly regular but has some anomalies. Knowing the distribution of the feature values becomes important when you use Machine Learning methods that assume a particular type of it, most often Gaussian.</p><p>In the above plot, we see that the variable <em>Total day minutes</em> is normally distributed, while <em>Total intl calls</em> is prominently skewed right (its tail is longer on the right).</p><p>There is also another, often clearer, way to grasp the distribution: <em>density plots</em> or, more formally, <em>Kernel Density Plots</em>. They can be considered a <a href="https://en.wikipedia.org/wiki/Kernel_smoother">smoothed</a> version of the histogram. Their main advantage over the latter is that they do not depend on the size of the bins. Let’s create density plots for the same two variables:</p><pre>df[features].<strong>plot</strong>(kind=&#39;density&#39;, subplots=True, <br>                  layout=(1, 2), sharex=False, figsize=(12, 4));</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QUjSH-ugzl_FXw7GSPqOkg.png" /></figure><p>It is also possible to plot a distribution of observations with seaborn&#39;s <a href="https://seaborn.pydata.org/generated/seaborn.distplot.html">distplot()</a>. For example, let&#39;s look at the distribution of <em>Total day minutes</em>. By default, the plot displays both the histogram with the <a href="https://en.wikipedia.org/wiki/Kernel_density_estimation">kernel density estimate</a> (KDE) on top.</p><pre>sns.<strong>distplot</strong>(df[&#39;Total intl calls&#39;]);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/822/1*4I9MAyi3ueSL5UxfhQV7pQ.png" /></figure><p>The height of the histogram bars here is normed and shows the density rather than the number of examples in each bin.</p><p><strong>Box plot</strong></p><p>Another useful type of visualization is a <em>box plot</em>. seaborn does a great job here:</p><pre>_, ax = plt.subplots(figsize=(3, 4))</pre><pre>sns.<strong>boxplot</strong>(data=df[&#39;Total intl calls&#39;], ax=ax);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*obasitTqoaJyKtZ7EEx0Ig.png" /></figure><p>Let’s see how to interpret a box plot. Its components are a <em>box</em> (obviously, this is why it is called a <em>box plot</em>), the so-called <em>whiskers</em>, and a number of individual points (<em>outliers</em>).</p><p>The box by itself illustrates the interquartile spread of the distribution; its length is determined by the 25th (Q1) and 75th (Q3) percentiles. The vertical line inside the box marks the median (50%) of the distribution.</p><p>The whiskers are the lines extending from the box. They represent the entire scatter of data points, specifically the points that fall within the interval (Q1−1.5⋅IQR, Q3+1.5⋅IQR), where IQR=Q3−Q1 is the <a href="https://en.wikipedia.org/wiki/Interquartile_range">interquartile range</a>.</p><p>Outliers that fall out of the range bounded by the whiskers are plotted individually as black points along the central axis.</p><p>We can see that a large number of international calls is quite rare in our data.</p><p><strong>Violin plot</strong></p><p>The last type of distribution plots that we will consider is a <em>violin plot</em>.</p><p>Look at the figures below. On the left, we see the already familiar box plot. To the right, there is a <em>violin plot</em> with the kernel density estimate on both sides.</p><pre>_, axes = plt.subplots(1, 2, sharey=True, figsize=(6, 4))</pre><pre>sns.<strong>boxplot</strong>(data=df[&#39;Total intl calls&#39;], ax=axes[0]); sns.<strong>violinplot</strong>(data=df[&#39;Total intl calls&#39;], ax=axes[1]);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*4PoRyAu-bKAHMLMh99uHqg.png" /></figure><p>The difference between the box and violin plots is that the former illustrates certain statistics concerning <em>individual examples</em> in a dataset while the violin plot concentrates more on the smoothed <em>distribution as a whole</em>.</p><p>In our case, the violin plot does not contribute any additional information about the data as everything is clear from the box plot alone.</p><p><strong>describe()</strong></p><p>In addition to graphical tools, in order to get the exact numerical statistics of the distribution, we can use the method <a href="https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.describe.html">describe()</a> of a DataFrame:</p><pre>df[features].<strong>describe</strong>()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*Fe_EtFJbjNZBvGiuxVBdLA.png" /></figure><p>Its output is mostly self-explanatory. <em>25%</em>, <em>50%</em> and <em>75%</em> are the corresponding <a href="https://en.wikipedia.org/wiki/Percentile">percentiles</a>.</p><h4>2.2 Categorical and binary features</h4><p><em>Categorical features</em> take on a fixed number of values. Each of these values assigns an observation to a corresponding group, known as a <em>category</em>, which reflects some qualitative property of this example. <em>Binary</em> variables are an important special case of categorical variables when the number of possible values is exactly 2. If the values of a categorical variable are ordered, it is called <em>ordinal</em>.</p><p><strong>Frequency table</strong></p><p>Let’s check the class balance in our dataset by looking at the distribution of the target variable: the <em>churn rate</em>. First, we will get a frequency table, which shows how frequent each value of the categorical variable is. For this, we will use the <a href="https://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.value_counts.html">value_counts()</a> method:</p><pre>df[&#39;Churn&#39;].<strong>value_counts</strong>()</pre><pre><em>False    2850<br>True      483<br>Name: Churn, dtype: int64</em></pre><p>By default, the entries in the output are sorted from the most to the least frequently-occurring values.</p><p>In our case, the data is not <em>balanced</em>; that is, our two target classes, loyal and disloyal customers, are not represented equally in the dataset. Only a small part of the clients canceled their subscription to the telecom service. As we will see in the following articles, this fact may imply some restrictions on measuring the classification performance, and, in the future, we may want to additionaly penalize our model errors in predicting the minority “Churn” class.</p><p><strong>Bar plot</strong></p><p>The bar plot is a graphical representation of the frequency table. The easiest way to create it is to use the seaborn&#39;s function <a href="https://seaborn.pydata.org/generated/seaborn.countplot.html">countplot()</a>. There is another function in seaborn that is somewhat confusingly called <a href="https://seaborn.pydata.org/generated/seaborn.barplot.html">barplot()</a> and is mostly used for representation of some basic statistics of a numerical variable grouped by a categorical feature.</p><p>Let’s plot the distributions for two categorical variables:</p><pre>_, axes = plt.subplots(nrows=1, ncols=2, figsize=(12, 4))</pre><pre>sns.<strong>countplot</strong>(x=&#39;Churn&#39;, data=df, ax=axes[0]);<br>sns.<strong>countplot</strong>(x=&#39;Customer service calls&#39;, data=df, ax=axes[1]);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AhpH10Bun1fAk2cUOWNL1w.png" /></figure><p>While the histograms, discussed above, and bar plots may look similar, there are several differences between them:</p><ol><li><em>Histograms</em> are best suited for looking at the distribution of numerical variables while <em>bar plots</em> are used for categorical features.</li><li>The values on the X-axis in the <em>histogram</em> are numerical; a <em>bar plot</em> can have any type of values on the X-axis: numbers, strings, booleans.</li><li>The <em>histogram</em>’s X-axis is a <em>Cartesian coordinate axis</em> along which values cannot be changed; the ordering of the <em>bars</em> is not predefined. Still, it is useful to note that the bars are often sorted by height, that is, the frequency of the values. Also, when we consider <em>ordinal</em> variables (like <em>Customer service calls</em> in our data), the bars are usually ordered by variable value.</li></ol><p>The left chart above vividly illustrates the imbalance in our target variable. The bar plot for <em>Customer service calls</em> on the right gives a hint that the majority of customers resolve their problems in maximum 2–3 calls. But, as we want to be able to predict the minority class, we may be more interested in how the fewer dissatisfied customers behave. It may well be that the tail of that bar plot contains most of our churn. These are just hypotheses for now, so let’s move on to some more interesting and powerful visual techniques.</p><h3>3. Multivariate visualization</h3><p><em>Multivariate</em> plots allow us to see relationships between two and more different variables, all in one figure. Just as in the case of univariate plots, the specific type of visualization will depend on the types of the variables being analyzed.</p><h4>3.1 Quantitative–Quantitative</h4><p>We are going to start with the interaction between quantitative variables.</p><p><strong>Correlation matrix</strong></p><p>Let’s look at the correlations among the numerical variables in our dataset. This information is important to know as there are Machine Learning algorithms (for example, linear and logistic regression) that do not handle highly correlated input variables well.</p><p>First, we will use the method <a href="https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.corr.html">corr()</a> on a DataFrame that calculates the correlation between each pair of features. Then, we pass the resulting <em>correlation matrix</em> to <a href="https://seaborn.pydata.org/generated/seaborn.heatmap.html">heatmap()</a> from seaborn, which renders a color-coded matrix for the provided values:</p><pre><em># Drop non-numerical variables<br></em>numerical = list(set(df.columns) - <br>                 set([&#39;State&#39;, &#39;International plan&#39;, <br>                      &#39;Voice mail plan&#39;, &#39;Area code&#39;, &#39;Churn&#39;,<br>                      &#39;Customer service calls&#39;]))</pre><pre><em># Calculate and plot<br></em>corr_matrix = df[numerical].<strong>corr</strong>()<br>sns.<strong>heatmap</strong>(corr_matrix);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GTn2GNLVhaqxB9hHESl0-w.png" /></figure><p>From the colored correlation matrix generated above, we can see that there are 4 variables such as <em>Total day charge</em> that have been calculated directly from the number of minutes spent on phone calls (<em>Total day minutes</em>). These are called <em>dependent</em> variables and can therefore be left out since they do not contribute any additional information. Let’s get rid of them:</p><pre>numerical = list(set(numerical) - <br>                 set([&#39;Total day charge&#39;, &#39;Total eve charge&#39;, <br>                      &#39;Total night charge&#39;, &#39;Total intl charge&#39;]))</pre><p><strong>Scatter plot</strong></p><p>The <em>scatter plot</em> displays values of two numerical variables as <em>Cartesian coordinates</em> in 2D space. Scatter plots in 3D are also possible.</p><p>Let’s try out the function <a href="https://matplotlib.org/devdocs/api/_as_gen/matplotlib.pyplot.scatter.html">scatter()</a> from the matplotlib library:</p><pre>plt.<strong>scatter</strong>(df[&#39;Total day minutes&#39;], df[&#39;Total night minutes&#39;]);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/826/1*5zCzJ_BgGgvCGWow4wbpdQ.png" /></figure><p>We get an uninteresting picture of two normally distributed variables. Also, it seems that these features are uncorrelated because the ellpise-like shape is aligned with the axes.</p><p>There is a slightly fancier option to create a scatter plot with the seaborn library:</p><pre>sns.<strong>jointplot</strong>(x=&#39;Total day minutes&#39;, y=&#39;Total night minutes&#39;,<br>              data=df, kind=&#39;scatter&#39;);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NUJTv-q87StT3PfuwPUSNg.png" /></figure><p>The function <a href="https://seaborn.pydata.org/generated/seaborn.jointplot.html">jointplot()</a> plots two histograms that may be useful in some cases. Using the same function, we can also get a smoothed version of our bivariate distribution:</p><pre>sns.<strong>jointplot</strong>(&#39;Total day minutes&#39;, &#39;Total night minutes&#39;,<br>              data=df, kind=&quot;kde&quot;, color=&quot;g&quot;);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5aPohQ0h1bbCNXGlkSKgIA.png" /></figure><p>This is basically a bivariate version of the <em>Kernel Density Plot</em> discussed earlier.</p><p><strong>Scatterplot matrix</strong></p><p>In some cases, we may want to plot a <em>scatterplot matrix</em> such as the one shown below. Its diagonal contains the distributions of the corresponding variables, and the scatter plots for each pair of variables fill the rest of the matrix.</p><pre><em># pairplot may become very slow with the SVG format<br></em>%config InlineBackend.figure_format = &#39;png&#39; sns.<strong>pairplot</strong>(df[numerical]);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7fpIP6dQA5EJ5-9xPkH5vA.png" /></figure><p>Sometimes, such visualization may help draw conclusions about data; but, in this case, everything is pretty clear with no surprises.</p><h4>3.2 Quantitative–Categorical</h4><p>In this section, we will make our simple quantitative plots a little more exciting. We will try to gain new insights for churn prediction from the interactions between the numerical and categorical features.</p><p>More specifically, let’s see how the input variables are related to the target variable Churn.</p><p>Previously, you learned about scatter plots. Additionally, their points can be color or size coded so that the values of a third categorical variable are also presented in the same figure. We can achieve this with the scatter() function seen above, but, let&#39;s try a new function called <a href="https://seaborn.pydata.org/generated/seaborn.lmplot.html">lmplot()</a> and use the parameter hue to indicate our categorical feature of interest:</p><pre>sns.<strong>lmplot</strong>(&#39;Total day minutes&#39;, &#39;Total night minutes&#39;, data=df, <br>           hue=&#39;Churn&#39;, fit_reg=False);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jVjLrlsxlrE--moF0zReqw.png" /></figure><p>It seems that our small proportion of disloyal customers lean towards the top-right corner; that is, such customers tend to spend more time on the phone during both day and night. But this is not absolutely clear, and we won’t make any definitive conclusions from this chart.</p><p>Now, let’s create box plots to visualize the distribution statistics of the numerical variables in two disjoint groups: the loyal customers (Churn=False) and those who left (Churn=True).</p><pre><em># Sometimes you can analyze an ordinal variable as numerical one<br></em>numerical.append(&#39;Customer service calls&#39;) </pre><pre>fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(10, 7))</pre><pre>for idx, feat in enumerate(numerical): <br>    ax = axes[int(idx / 4), idx % 4] <br>    sns.<strong>boxplot</strong>(x=&#39;Churn&#39;, y=feat, data=df, ax=ax) <br>    ax.set_xlabel(&#39;&#39;) <br>    ax.set_ylabel(feat) <br>fig.tight_layout();</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d75o4ohM8vfouFXWHQV6Yg.png" /></figure><p>From this chart, we can see that the greatest discrepancy in distribution between the two groups is for three variables: <em>Total day minutes</em>, <em>Customer service calls</em>, and <em>Number vmail messages</em>. Later in this course, we will learn how to determine feature importance in classification using <em>Random Forest</em> or <em>Gradient Boosting</em>; there, we will see that the first two features are indeed very important for churn prediction.</p><p>Let’s look at the distribution of day minutes spoken for the loyal and disloyal customers separately. We will create box and violin plots for <em>Total day minutes</em> grouped by the target variable.</p><pre>_, axes = plt.subplots(1, 2, sharey=True, figsize=(10, 4)) </pre><pre>sns.<strong>boxplot</strong>(x=&#39;Churn&#39;, y=&#39;Total day minutes&#39;,<br>            data=df, ax=axes[0]);<br>sns.<strong>violinplot</strong>(x=&#39;Churn&#39;, y=&#39;Total day minutes&#39;, <br>               data=df, ax=axes[1]);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Cfk88kF8Tb02glUV0wjPIA.png" /></figure><p>In this case, the violin plot does not contribute any additional information about our data as everything is clear from the box plot alone: disloyal customers tend to talk on the phone more.</p><p><strong>An interesting observation</strong>: on average, customers that discontinue their contracts are more active users of communication services. Perhaps they are unhappy with the tariffs, so a possible measure to prevent churn could be a reduction in call rates. The company will need to undertake additional economic analysis to find out whether such measures would be beneficial.</p><p>When we want to analyze a quantitative variable in two categorical dimensions at once, there is a suitable function for this in the seaborn library called <a href="https://seaborn.pydata.org/generated/seaborn.factorplot.html">factorplot()</a>. For example, let&#39;s visualize the interaction between <em>Total day minutes</em> and two categorical variables in the same plot:</p><pre>sns.<strong>factorplot</strong>(x=&#39;Churn&#39;, y=&#39;Total day minutes&#39;,<br>               col=&#39;Customer service calls&#39;,<br>               data=df[df[&#39;Customer service calls&#39;] &lt; 8], <br>               kind=&quot;box&quot;, col_wrap=4, size=3, aspect=.8);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b4QMiiKjkGxTRXg_Ep6LMw.png" /></figure><p>From this, we could conclude that, starting with 4 calls, <em>Total day minutes</em> may no longer be the main factor for customer churn. Perhaps, in addition to our previous guess about the tariffs, there are customers that are dissatisfied with the service due to other problems, which might lead to fewer number of day minutes spent on calls.</p><h4>3.3 Categorical–Categorical</h4><p>As we saw earlier in this article, the variable <em>Customer service calls</em> has few unique values and, thus, can be considered either numerical or ordinal. We have already seen its distribution with a <em>count plot</em>. Now, we are interested in the relationship between this ordinal feature and the target variable <em>Churn</em>.</p><p>Let’s look at the distribution of the number of calls to the customer service, again using a <em>count plot</em>. This time, let’s also pass the parameter hue=Churn that adds a categorical dimension to the plot:</p><pre>sns.<strong>countplot</strong>(x=&#39;Customer service calls&#39;, hue=&#39;Churn&#39;, data=df);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/898/1*J4udJmxfMS7kURxiyX3uKQ.png" /></figure><p><strong>An observation</strong>: the churn rate increases significantly after 4 or more calls to the customer service.</p><p>Now, let’s look at the relationship between <em>Churn</em> and the binary features, <em>International plan</em> and <em>Voice mail plan</em>.</p><pre>_, axes = plt.subplots(1, 2, sharey=True, figsize=(10, 4)) </pre><pre>sns.<strong>countplot</strong>(x=&#39;International plan&#39;, hue=&#39;Churn&#39;,<br>              data=df, ax=axes[0]);<br>sns.<strong>countplot</strong>(x=&#39;Voice mail plan&#39;, hue=&#39;Churn&#39;,<br>              data=df, ax=axes[1]);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LsESjZtHl4C2uGF3Yy6QDQ.png" /></figure><p><strong>An observation</strong>: when <em>International Plan</em> is enabled, the churn rate is much higher; the usage of the international plan by the customer is a strong feature. We do not observe the same effect with <em>Voice mail plan</em>.</p><p><strong>Contingency table</strong></p><p>In addition to using graphical means for categorical analysis, there is a traditional tool from statistics: a <em>contingency table</em>, also called a <em>cross tabulation</em>. It represents multivariate frequency distribution of categorical variables in tabular form. In particular, it allows us to see the distribution of one variable conditional on the other by looking along a column or row.</p><p>Let’s try to see how <em>Churn</em> is related to the categorical variable <em>State</em> by creating a cross tabulation:</p><pre>pd.<strong>crosstab</strong>(df[&#39;State&#39;], df[&#39;Churn&#39;]).T</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eX4xJ5mJ84PrZdqR5dX0Pg.png" /></figure><p>In the case of <em>State</em>, the number of distinct values is rather high: 51. We see that there are only a few data points available for each individual state — only 3 to 17 customers in each state abandoned the operator. Let’s ignore that for a second and calculate the churn rate for each state, sorting it from high to low:</p><pre>df.<strong>groupby</strong>([&#39;State&#39;])[&#39;Churn&#39;].<br>   <strong>agg</strong>([np.mean]).<br>   <strong>sort_values</strong>(by=&#39;mean&#39;, ascending=False).T</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EJzIgSoNTGrzCf7v7KhaRw.png" /></figure><p>At first glance, it seems that the churn rate in <em>New Jersey</em> and <em>California</em> are above 25% and less than 6% for Hawaii and Alaska. However, these conclusions are based on too few examples, and our observation could be a mere property of our particular dataset. We can confirm this with the <a href="https://en.wikipedia.org/wiki/Matthews_correlation_coefficient">Matthews</a> and <a href="https://en.wikipedia.org/wiki/Cram%C3%A9r%27s_V">Cramer</a> correlation hypotheses, but this would be beyond the scope of this article.</p><h3>4. Whole dataset</h3><h4>4.1 Naive approach</h4><p>We have been looking at different <em>facets</em> of our dataset by guessing interesting features and selecting a small number of them at a time for visualization. We have only delt with two to three variables at once and were easily able to observe the structure and relationships in data. But, what if we want to display all the features and still be able to interpret the resulting visualization?</p><p>We could use hist() or create a scatterplot matrix with pairplot() for the whole dataset to look at all of our features simultaneously. But, when the number of features is high enough, this kind of visual analysis quickly becomes slow and inefficient. Besides, we would still be analyzing our variables in a pairwise fashion, not all at once.</p><h4>4.2 Dimensionality reduction</h4><p>Most real-world datasets have many features, sometimes, many thousands of them. Each of them can be considered as a dimension in the space of data points. Consequently, more often than not, we deal with high-dimensional datasets, where entire visualization is quite hard.</p><p>To look at a dataset as a whole, we need to decrease the number of dimensions used in visualization without losing much information about data. This task is called <em>dimensionality reduction</em> and is an example of an <em>unsupervised learning</em> problem because we need to derive new, low-dimensional features from the data itself, without any supervised input.</p><p>One of the well-known dimensionality reduction methods is <em>Principal Component Analysis</em> (PCA), which we will study later in this course. Its limitation is that it is a <em>linear</em> algorithm that implies certain restrictions on the data.</p><p>There are also many non-linear methods, collectively called <em>Manifold Learning</em>. One of the best-known of them is <em>t-SNE</em>.</p><h4>4.3 t-SNE</h4><p>Let’s create a <a href="https://en.wikipedia.org/wiki/T-distributed_stochastic_neighbor_embedding">t-SNE</a> representation of the same churn data we have been using.</p><p>The name of the method looks complex and a bit intimidating: <em>t-distributed Stohastic Neighbor Embedding</em>. Its math is also impressive (we will not delve into it here, but, if you feel brave, here is the <a href="http://www.jmlr.org/papers/volume9/vandermaaten08a/vandermaaten08a.pdf">original article</a> by Laurens van der Maaten and Geoffrey Hinton from <a href="http://www.jmlr.org/">JMLR</a>). Its basic idea is simple: to find a projection for a high-dimensional feature space onto a plane (or a 3D hyperplane, but it is almost always 2D) such that those points that were far apart in the initial n-dimensional space will end up far apart on the plane. Those that were originally close would remain close to each other.</p><p>Essentially, <em>neighbor embedding</em> is a search for a new and less-dimensional data representation that preserves neighborship of examples.</p><p>Now, let’s do some practice. First, we need to import some additional classes:</p><pre>from sklearn.manifold import TSNE<br>from sklearn.preprocessing import StandardScaler</pre><p>We will leave out the <em>State</em> and <em>Churn</em> features and convert the values “Yes”/”No” of the binary features into numerical values using <a href="http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.map.html">pandas.Series.map()</a>:</p><pre>X = df.<strong>drop</strong>([&#39;Churn&#39;, &#39;State&#39;], axis=1)<br>X[&#39;International plan&#39;] = X[&#39;International plan&#39;].<br>                          <strong>map</strong>({&#39;Yes&#39;: 1, &#39;No&#39;: 0})<br>X[&#39;Voice mail plan&#39;] = X[&#39;Voice mail plan&#39;].<br>                       <strong>map</strong>({&#39;Yes&#39;: 1, &#39;No&#39;: 0})</pre><p>We also need to normalize the data. For this, we will subtract the mean from each variable and divide it by its standard deviation. All of this can be done with StandardScaler.</p><pre>scaler = StandardScaler()<br>X_scaled = scaler.<strong>fit_transform</strong>(X)</pre><p>Now, let’s build a t-SNE representation:</p><pre>%%time tsne = TSNE(random_state=17)<br>tsne_repr = tsne.<strong>fit_transform</strong>(X_scaled)</pre><pre><em>CPU times: user 1min 32s, sys: 7.86 s, total: 1min 39s<br>Wall time: 1min 39s</em></pre><p>and plot it:</p><pre>plt.<strong>scatter</strong>(tsne_repr[:, 0], tsne_repr[:, 1]);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/816/1*v2i3ftWB2KULDZmBMwnK0g.png" /></figure><p>Let’s color this t-SNE representation according to the churn (green for loyal customers, and red for those who left).</p><pre>plt.<strong>scatter</strong>(tsne_repr[:, 0], tsne_repr[:, 1], <br>            c=df[&#39;Churn&#39;].map({False: &#39;green&#39;, True: &#39;red&#39;}));</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/828/1*gNEzT6kjj2KQaKgza9yN_A.png" /></figure><p>We can see that customers who churned are concentrated in a few areas of the lower dimensional feature space.</p><p>To better understand the picture, we can also color it with the remaining binary features: <em>International Plan</em> and <em>Voicemail</em>. The green dots here indicate the objects that are positive for the corresponding binary feature.</p><pre>_, axes = plt.subplots(1, 2, sharey=True, figsize=(12, 5))</pre><pre>for i, name in enumerate([&#39;International plan&#39;, &#39;Voice mail plan&#39;]):<br>    axes[i].<strong>scatter</strong>(tsne_repr[:, 0], tsne_repr[:, 1],<br>                    c=df[name].<strong>map</strong>({&#39;Yes&#39;: &#39;green&#39;, &#39;No&#39;: &#39;red&#39;}))<br>    axes[i].set_title(name)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FHLtGK9mM583wZiUrx1XFQ.png" /></figure><p>Now it is clear that, for example, many dissatisfied customers who canceled their subscription are crowded together in the most south-west cluster that represents the people with the international plan but no voice mail.</p><p>Finally, let’s note some disadvantages of t-SNE:</p><ul><li>High computational complexity. The <a href="http://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html">implementation</a> in scikit-learn is unlikely to be feasible in a real task. If you have a large number of samples, you should try <a href="https://github.com/DmitryUlyanov/Multicore-TSNE">Multicore-TSNE</a> instead.</li><li>The plot can change a great deal depending on the random seed, which complicates interpretation. <a href="http://distill.pub/2016/misread-tsne/">Here</a> is a good tutorial on t-SNE. In general, you shouldn’t make any far-reaching conclusions based on such graphs because it can equate to plain guessing. Of course, some findings in t-SNE pictures can inspire an idea and be confirmed through more thorough research down the line, but that does not happen very often.</li></ul><p>Occasionally, using t-SNE, you can get a really good intuition for the data. The following is a good paper that shows an example of this for handwritten digits: <a href="https://colah.github.io/posts/2014-10-Visualizing-MNIST/">Visualizing MNIST</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/560/1*H3i1f9DvXBDT75FQsxhFeA.png" /></figure><p>Sometimes t-SNE really helps to understand something, and sometimes you can just draw a Christmas tree toy :-)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PE3G4Zc6eEAkePdrEtNrnQ.png" /></figure><h3>5. Assignment #2</h3><p>Full versions of assignments are announced each week in a new run of the course (October 1, 2018). Meanwhile, you can practice with a demo version: <a href="https://www.kaggle.com/kashnitsky/assignment-2-analyzing-cardiovascular-data">Kaggle Kernel</a>, <a href="https://mlcourse.ai/notebooks/blob/master/jupyter_english/assignments_demo/assignment02_analyzing_cardiovascular_desease_data.ipynb?flush_cache=true">nbviewer</a>.</p><h3>6. Useful resources</h3><ul><li>Here is the official documentation for the libraries we used: <a href="https://matplotlib.org/contents.html">matplotlib</a>, <a href="https://seaborn.pydata.org/introduction.html">seaborn</a> and <a href="https://pandas.pydata.org/pandas-docs/stable/">pandas</a>.</li><li>The <a href="http://seaborn.pydata.org/examples/index.html">gallery</a> of sample charts created with seaborn is a very good resource.</li><li>Also, see the <a href="http://scikit-learn.org/stable/modules/manifold.html">documentation</a> on Manifold Learning in scikit-learn</li><li>Efficient t-SNE implementation <a href="https://github.com/DmitryUlyanov/Multicore-TSNE">Multicore-TSNE</a>.</li><li>“How to Use t-SNE Effectively”, <a href="https://distill.pub/2016/misread-tsne/">Distill.pub</a>.</li></ul><p><em>Authors: </em><a href="http://linkedin.com/in/egor-polusmak/"><em>Egor Polusmak</em></a><em>, and </em><a href="https://www.linkedin.com/in/festline/"><em>Yury Kashnitskiy</em></a><em>. Translated and edited by </em><a href="https://www.linkedin.com/in/yuanyuanpao/"><em>Yuanyuan Pao</em></a><em>, </em><a href="https://www.linkedin.com/in/christinabutsko/"><em>Christina Butsko</em></a><em>, </em><a href="https://www.linkedin.com/in/anastasiamanokhina/"><em>Anastasia Manokhina</em></a><em>, </em><a href="https://www.linkedin.com/in/inga-kaidanova-a92398b1/"><em>Inga Kaydanova</em></a><em>, and </em><a href="https://www.linkedin.com/in/datamove/"><em>Artem Trunov</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=846b989675cd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-2-visual-data-analysis-in-python-846b989675cd">Open Machine Learning Course. Topic 2. Visual Data Analysis with Python</a> was originally published in <a href="https://medium.com/open-machine-learning-course">Open Machine Learning Course</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>