<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Vlad Niculae</title><link href="//vene.ro/" rel="alternate"/><link href="//vene.ro/feeds/all.atom.xml" rel="self"/><id>//vene.ro/</id><updated>2020-09-14T00:00:00+02:00</updated><entry><title>Optimizing with constraints: reparametrization and geometry.</title><link href="//vene.ro/blog/mirror-descent.html" rel="alternate"/><published>2020-09-14T00:00:00+02:00</published><updated>2020-09-14T00:00:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2020-09-14:/blog/mirror-descent.html</id><summary type="html">&lt;p&gt;Some of the most popular strategies for handling constraints in gradient-based
optimization, namely: reparametrization, projected gradient, and mirror descent,
while seemingly very different, are deeply connected. In this post, we will
explore these connections.
In particular, we show that mirror descent is equivalent to gradient descent on
a reparametrized objective with straight-through&amp;nbsp;gradients.&lt;/p&gt;</summary><content type="html">&lt;p&gt;When training machine learning models, and deep networks in particular,
we typically use gradient-based methods. But if we require the weights to
satisfy some constraints, things quickly get more&amp;nbsp;complicated.&lt;/p&gt;
&lt;p&gt;Some of the most popular strategies for handling constraints, while seemingly
very different at first sight, are deeply connected. In this post, we will
explore these connections and demonstrate them in&amp;nbsp;PyTorch.&lt;/p&gt;
&lt;p&gt;In particular, we show that mirror descent is equivalent to gradient descent on
a reparametrized objective with straight-through gradients: replacing a
constrained&amp;nbsp;variable &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt; with some squishing&amp;nbsp;function &lt;span class="arithmatex"&gt;\(\sigma(u)\)&lt;/span&gt;, but&amp;nbsp;treating
&lt;span class="arithmatex"&gt;\(\sigma\)&lt;/span&gt; as if it were the identity in the backward&amp;nbsp;pass.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Outline.&lt;/strong&gt; &lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#why-are-constraints-challenging"&gt;Why are constraints&amp;nbsp;challenging?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ways-to-deal-with-constraints"&gt;Ways to deal with constraints.&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#reparametrization"&gt;Reparametrization.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#projected-gradient"&gt;Projected&amp;nbsp;gradient.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#generalizing-the-projected-gradient-method-with-divergences"&gt;Generalizing the projected gradient method with&amp;nbsp;divergences.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#duality-between-mirror-descent-and-natural-gradient"&gt;Duality between mirror descent and&amp;nbsp;natural&amp;nbsp;gradient.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusions"&gt;Conclusions.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#acknowledgements"&gt;Acknowledgements.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;script src="https://unpkg.com/d3@3/d3.min.js"&gt;&lt;/script&gt;
&lt;style type="text/css"&gt;

#plotdiv {text-align: center;}

.rules line, .rules path {
  shape-rendering: crispEdges;
  stroke: #00000;
}

.series path {
  fill: none;
  stroke: #348;
}

.labels {
    font-family: sans-serif;
    font-size: .7em;
}

.thick {
  stroke-width: 4px;
}

.dashed {
    stroke-width: 1px;
}

.unconstr{ fill: gray };
.constr{ fill: black };
&lt;/style&gt;

&lt;div class="arithmatex"&gt;\[
\newcommand\pfrac[2]{\frac{\partial #1}{\partial #2}}
\newcommand\DP[2]{\left\langle #1, #2 \right\rangle}
\]&lt;/div&gt;
&lt;h1 id="why-are-constraints-challenging"&gt;Why are constraints challenging?&lt;a class="headerlink" href="#why-are-constraints-challenging" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;In machine learning, we fit models to data by minimizing an&amp;nbsp;objective,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[\min_{x \in \mathcal{X}} f(x)\,. \tag{OPT}\]&lt;/div&gt;
&lt;p&gt;Here, &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt; denotes the parameters to be learned, for instance, the neural network weights.
They typically take values&amp;nbsp;in &lt;span class="arithmatex"&gt;\(\mathcal{X}=\reals\)&lt;/span&gt;, and we train networks by
some variant of the &lt;em&gt;gradient&lt;/em&gt; method: we choose an initial&amp;nbsp;configuration &lt;span class="arithmatex"&gt;\(x^{(0)}\)&lt;/span&gt;
and successively applying updates of the&amp;nbsp;form:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[
x^{(t+1)} \leftarrow x^{(t)} - \alpha^{(t)} \nabla f(x^{(t)})\,.
\]&lt;/div&gt;
&lt;p&gt;In deep learning, we typically use stochastic flavors that
nonetheless perform well and are efficient.  Here, we will focus on a relatively
nice example: a convex quadratic&amp;nbsp;function &lt;span class="arithmatex"&gt;\(f\)&lt;/span&gt;.
We will see that, even in this case, constraints quickly complicate&amp;nbsp;things.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why constrain?&lt;/strong&gt;
For modeling reasons, we might want to impose restrictions on some of the&amp;nbsp;weights
&lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt;.  Perhaps one of the parameter corresponds to the variance of a
distribution, so it cannot be negative. Or perhaps a parameter denotes
some sort of &amp;#8220;gate&amp;#8221;, or mixture between two&amp;nbsp;alternatives &lt;span class="arithmatex"&gt;\(xa_1 + (1-x)a_2\)&lt;/span&gt;. 
In this case, we would need to&amp;nbsp;constrain &lt;span class="arithmatex"&gt;\(x \in [0, 1]\)&lt;/span&gt;. This is often called a
&lt;em&gt;box constraint&lt;/em&gt; and it is one of the most friendly types of inequality
constraint we might deal&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;For one-dimensional convex problems, &lt;em&gt;i.e.,&lt;/em&gt; &lt;span class="arithmatex"&gt;\(\mathcal{X} = [a, b] \subset
\reals\)&lt;/span&gt;, box constraints do not complicate the problem: we can solve the
unconstrained&amp;nbsp;problem &lt;span class="arithmatex"&gt;\(x_{\text{unc}}^* = \argmin_{x\in\reals} f(x)\)&lt;/span&gt;.  If the
answer satisfies the constraint, then it must be the solution of the constrained
problem as well. If not, the answer can be found by &lt;em&gt;clipping&lt;/em&gt; to the&amp;nbsp;interval:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ x^\star = \operatorname{clip}_{[a,b]}(x_\text{unc}^\star)
\coloneqq \max(a, \min(b, x_\text{unc}^\star))\,.
\]&lt;/div&gt;
&lt;details class="note"&gt;
&lt;summary&gt;Proof&lt;/summary&gt;
&lt;p&gt;We add non-negative dual&amp;nbsp;variables &lt;span class="arithmatex"&gt;\(\mu_a\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\(\mu_b\)&lt;/span&gt; to handle the inequality&amp;nbsp;constraints &lt;span class="arithmatex"&gt;\(x \geq a\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\(x \leq b\)&lt;/span&gt;, and write the&amp;nbsp;Lagrangian,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[\mathcal{L}(x) = f(x) + \mu_a (a-x) + \mu_b(x-b)\,.\]&lt;/div&gt;
&lt;p&gt;An&amp;nbsp;optimal &lt;span class="arithmatex"&gt;\(x^\star\)&lt;/span&gt; must satisfiy the original&amp;nbsp;constraints &lt;span class="arithmatex"&gt;\((a \leq x^\star \leq b)\)&lt;/span&gt;
and be a stationary point of the&amp;nbsp;Lagrangian:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ 
D_x \mathcal{L}(x^\star) = 0 \iff f'(x^\star) = \mu_a - \mu_b\,.
\tag{F}
\]&lt;/div&gt;
&lt;p&gt;The dual variables must be non-negative and satisfy
complementary&amp;nbsp;slackness:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[
\mu_a (a - x^\star) = 0, \quad\text{and}\quad \mu_b (x^\star - b) = 0\,.
\]&lt;/div&gt;
&lt;p&gt;Let &lt;span class="arithmatex"&gt;\(x^\star_\text{unc}\)&lt;/span&gt; be the solution of the unconstrained problem,&amp;nbsp;satisfying  &lt;span class="arithmatex"&gt;\(f'(x^\star_\text{unc})=0\)&lt;/span&gt;.&amp;nbsp;If 
&lt;span class="arithmatex"&gt;\(a \leq x^\star_\text{unc} \leq b\)&lt;/span&gt;,&amp;nbsp;then &lt;span class="arithmatex"&gt;\(x^\star=x^\star_\text{unc}\)&lt;/span&gt;, and&amp;nbsp;choosing &lt;span class="arithmatex"&gt;\(\mu_a=\mu_b=0\)&lt;/span&gt; satisfies all&amp;nbsp;conditions.&lt;/p&gt;
&lt;p&gt;Otherwise, &lt;span class="arithmatex"&gt;\(x^\star_\text{unc}\)&lt;/span&gt; is either too small or too large.&amp;nbsp;Assume &lt;span class="arithmatex"&gt;\(x^\star_\text{unc} &amp;gt; a\)&lt;/span&gt; and&amp;nbsp;take &lt;span class="arithmatex"&gt;\(x^\star = a\)&lt;/span&gt;. Then we&amp;nbsp;have &lt;span class="arithmatex"&gt;\(\mu_b=0\)&lt;/span&gt;,
and from (F) we must&amp;nbsp;have &lt;span class="arithmatex"&gt;\(\mu_a = f'(a)\)&lt;/span&gt;. Is this a valid value for the
dual variable? We must check&amp;nbsp;that &lt;span class="arithmatex"&gt;\(f'(a) \geq 0\)&lt;/span&gt;.&amp;nbsp;Convex &lt;span class="arithmatex"&gt;\(f\)&lt;/span&gt; satisfies
&lt;span class="arithmatex"&gt;\( f(a) - f(x) \leq f'(a)(a-x) \)&lt;/span&gt;
for&amp;nbsp;any &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt;, including the&amp;nbsp;minimizer &lt;span class="arithmatex"&gt;\(x=x^\star_\text{unc}\)&lt;/span&gt;.
By&amp;nbsp;assumption, &lt;span class="arithmatex"&gt;\(a-x^\star_\text{unc} &amp;gt; 0\)&lt;/span&gt;, so we may divide by it&amp;nbsp;yielding&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \mu_a = f'(a) \geq \frac{f(a)-f(x^\star_\text{unc})}{a-x^\star_\text{unc}} \geq 0. \]&lt;/div&gt;
&lt;p&gt;The&amp;nbsp;case &lt;span class="arithmatex"&gt;\( x^\star_\text{unc} &amp;gt; b \)&lt;/span&gt; follows&amp;nbsp;similarly.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;The following interactive demo might convince you that this is true. We show the
1-d&amp;nbsp;function &lt;span class="arithmatex"&gt;\(f(x) = (x - x_0)^2 / 2\)&lt;/span&gt; and its minimizer constrained&amp;nbsp;to &lt;span class="arithmatex"&gt;\([0, 1]\)&lt;/span&gt;.
Drag the slider to change the location of the unconstrained&amp;nbsp;minimizer &lt;span class="arithmatex"&gt;\(x_0\)&lt;/span&gt;.&lt;/p&gt;
&lt;div id="plotdiv"&gt;
  &lt;svg id="onedimplot" preserveAspectRatio="xMinYMin meet" viewBox="0 0 550 150"&gt;&lt;/svg&gt; &lt;br /&gt;
    &lt;input type="range" min="-1" max="2" step=".001" oninput="plot(this.value)" onchange="plot(this.value)"&gt;
  &lt;/div&gt;

&lt;script&gt;
var w = 500;
var h = 100;

var x = d3.scale.linear().domain([-2, 3]).range([0, w]);
var xint = d3.scale.linear().domain([0, 1]);
var y = d3.scale.linear().domain([ 0, 1]).range([h, 0]);

//  var svg = d3.select("body").append("svg")
//  .attr("width", w + 50)
//  .attr("height", h + 50);
var svg = d3.select("#onedimplot");
    var vis = svg.append("svg:g").attr("transform", "translate(25,25)")
    make_rules();

    plot(0.5);


    function plot(x0) {
        var quadratic = make_quadratic(x0);
        chart_line(quadratic, x0);
    }

    function make_quadratic(x0) {
            return (function(t) {
                    return 0.5 * (t - x0) * (t - x0) + 0.2;
            });
    }

function chart_line(func, x0) {
            vis.selectAll('.dots').remove();
            vis.selectAll('.series').remove();
    var g = vis.append("svg:g").classed("series", true)

    g.append("svg:path")
        .classed("dashed", true)
        .attr("d", function(d) { return d3.svg.line()(
          x.ticks(100).map(function(t) {
            return [ x(t), y(func(t)) ]
          })
         )})

    g.append("svg:path")
        .classed("thick", true)
        .attr("d", function(d) { return d3.svg.line()(
          xint.ticks(100).map(function(t) {
            return [ x(t), y(func(t)) ]
          })
                    )})


            vis.append('circle')
                .classed("dots", true)
                .classed("unconstr", true)
                .attr("r", 5)
                .attr("cx", function(d) { return x(x0); })
                .attr("cy", function(d) { return y(func(x0)); })

          var xstar = Math.min(Math.max(x0, 0), 1);

            vis.append('circle')
                .classed("dots", true)
                .classed("constr", true)
                .attr("r", 5)
                .attr("cx", function(d) { return x(xstar); })
                .attr("cy", function(d) { return y(func(xstar)); })
}

function make_rules() {
    var rules = vis.append("svg:g").classed("rules", true)

    function make_x_axis() {
        return d3.svg.axis()
                .scale(x)
                .orient("bottom")
                .ticks(10)
    }


    rules.append("svg:g").classed("grid x_grid", true)
            .attr("transform", "translate(0,"+h+")")
            .call(make_x_axis()
                .tickSize(0,0,0)
                .tickFormat("")
            )

    rules.append("svg:g").classed("labels x_labels", true)
            .attr("transform", "translate(0,"+h+")")
            .call(make_x_axis()
                .tickSize(1)
            )
}
&lt;/script&gt;

&lt;p&gt;However, in dimension two or more, clipping no longer works, because of
interactions between the variables. We demonstate this on a quadratic problem
which will become the main focus of the rest of this&amp;nbsp;post,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \min_{x \in \mathcal{X}} 
f(x) \coloneqq \frac{1}{2}~(x - x_0)^\top Q (x - x_0)\,.\tag{QP} \]&lt;/div&gt;
&lt;p&gt;Let us visualize this problem for the specific case of
the unit&amp;nbsp;square &lt;span class="arithmatex"&gt;\(\mathcal{X} = [0,1] \times [0,1] \subset \reals^2\)&lt;/span&gt;,&amp;nbsp;with &lt;span class="arithmatex"&gt;\(x_0 = (1.5, .1)\)&lt;/span&gt;,&amp;nbsp;and 
&lt;span class="arithmatex"&gt;\(Q = \left(\begin{smallmatrix}3 &amp;amp; 2 \\\\ 2 &amp;amp; 3 \end{smallmatrix}\right)\)&lt;/span&gt;.
If not for the constraints,&amp;nbsp;since &lt;span class="arithmatex"&gt;\(Q\)&lt;/span&gt; is positive definite, the minimum
would&amp;nbsp;be &lt;span class="arithmatex"&gt;\(x^\star_\text{unc} = x_0\)&lt;/span&gt;.&lt;label for="sn-1" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-1" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;Because &lt;span class="arithmatex"&gt;\(f(x_0)=0,\)&lt;/span&gt; and positive
definiteness&amp;nbsp;guarantess &lt;span class="arithmatex"&gt;\(f(x) &amp;gt; 0\)&lt;/span&gt; for&amp;nbsp;any &lt;span class="arithmatex"&gt;\(x \neq x_0\)&lt;/span&gt;.&lt;/span&gt;
But a contour plot shows that the constrained&amp;nbsp;minimum &lt;span class="arithmatex"&gt;\(x^\star\)&lt;/span&gt; is not the same as the
result of clipping the unconstrained minimum to the&amp;nbsp;box.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Contour plot of a skewed quadratic function, with three distinct
points marked: the unconstrained minimizer, the minimizer constrained to the
0-1 box, and the projection of the unconstrained minimizer." src="/images/mirror_quad_landscape.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;Constraint handling cannot be left as an afterthought: 
it needs to be baked into the optimization strategy.&lt;label for="sn-2" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-2" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;
There is an important special case where (&lt;span class="caps"&gt;QP&lt;/span&gt;) can be solved exactly: the&amp;nbsp;case &lt;span class="arithmatex"&gt;\(Q = I\)&lt;/span&gt;. In this case, the problem&amp;nbsp;becomes
&lt;span class="arithmatex"&gt;\(\argmin_{x \in \mathcal{X}} \| x - x_0 \|^2_2,\)&lt;/span&gt;
which is known as the &lt;em&gt;Euclidean projection&lt;/em&gt;&amp;nbsp;of &lt;span class="arithmatex"&gt;\(x_0\)&lt;/span&gt; onto &lt;span class="arithmatex"&gt;\(\mathcal{X}\)&lt;/span&gt;.&amp;nbsp;If &lt;span class="arithmatex"&gt;\(\mathcal{X}\)&lt;/span&gt; are box constraints, the projection decomposes into a series of
independent 1-d projections, which we&amp;#8217;ve seen can be solved by&amp;nbsp;clipping.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As a warm up, let us implement our quadratic&amp;nbsp;function
&lt;span class="arithmatex"&gt;\(f(x) = \frac{1}{2} {(x - x_0)^\top}Q{(x - x_0)}\)&lt;/span&gt; in PyTorch, 
as well as a minimal gradient descent loop from&amp;nbsp;scratch.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;torch&lt;/span&gt;

&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_default_dtype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;
    &lt;span class="n"&gt;Qz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Qz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;optim_grad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x_init&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_grad_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This procedure quickly converges&amp;nbsp;to &lt;span class="arithmatex"&gt;\(x^\star_\text{unc} = (1.5, .1)\)&lt;/span&gt;.&lt;/p&gt;
&lt;h1 id="ways-to-deal-with-constraints"&gt;Ways to deal with constraints.&lt;a class="headerlink" href="#ways-to-deal-with-constraints" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;When faced with a box-constrained optimization problem, these are the ideas that
most practitioners would turn to.&lt;label for="sn-3" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-3" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;This turns out to be a handy personality
quiz to see if somebody resonates more with neural networks or with convex&amp;nbsp;optimization.&lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Reparamtrization (&lt;span class="caps"&gt;REP&lt;/span&gt;).&lt;/em&gt; Circumvent the constraints&amp;nbsp;on &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt;, 
    by expressing the function in terms of unconstrained&amp;nbsp;variables &lt;span class="arithmatex"&gt;\(u\)&lt;/span&gt;
    such&amp;nbsp;that &lt;span class="arithmatex"&gt;\(x_i = \sigma(u_i)\)&lt;/span&gt;,&amp;nbsp;where &lt;span class="arithmatex"&gt;\(\sigma : \reals \to \mathcal{X}\)&lt;/span&gt; is a
    &amp;#8220;squishing&amp;#8221; nonlinearity.
    We can then perform unconstrained minimiziation&amp;nbsp;on &lt;span class="arithmatex"&gt;\(f \circ \sigma\)&lt;/span&gt;.&amp;nbsp;For &lt;span class="arithmatex"&gt;\(\mathcal{X}=[0,1]^d\)&lt;/span&gt;, we may use the logistic
    function,&lt;label for="sn-4" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-4" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;General&amp;nbsp;intervals &lt;span class="arithmatex"&gt;\([a,b]\)&lt;/span&gt; are obtained by affinely&amp;nbsp;transforming &lt;span class="arithmatex"&gt;\([0,1]\)&lt;/span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \sigma(u) = \frac{1}{1 + \exp(-u)}\,.\]&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Projected gradient (&lt;span class="caps"&gt;PG&lt;/span&gt;).&lt;/em&gt; Perform unconstrained gradient updates, then
    project back onto the domain after each&amp;nbsp;update:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[
\begin{aligned}
x^{(t+½)} &amp;amp;\leftarrow x^{(t)} - \alpha^{(t)} \nabla f(x^{(t)})\,, \\
x^{(t+1)} &amp;amp;\leftarrow \operatorname{Proj}_\mathcal{X}\big(x^{(t+½)}\big)\,.
\\
\end{aligned}
\]&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span class="caps"&gt;REP&lt;/span&gt; is convenient when working with neural network libraries like PyTorch,
because it can be implemented just by changing our model, without requiring
modifications to the optimization code. However, the resulting problem (after
reparametrization) is no longer convex&amp;nbsp;in &lt;span class="arithmatex"&gt;\(u\)&lt;/span&gt;, even if the original problem was
convex&amp;nbsp;in &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt;. &lt;span class="caps"&gt;PG&lt;/span&gt; directly solves the convex optimization problem (&lt;span class="caps"&gt;QP&lt;/span&gt;), but
the intermediate&amp;nbsp;iterates &lt;span class="arithmatex"&gt;\(x^{(t+0.5)}\)&lt;/span&gt; can&amp;nbsp;leave &lt;span class="arithmatex"&gt;\(\mathcal{X}\)&lt;/span&gt;,
leading to a possibly less stable or too aggressive&amp;nbsp;trajectory.&lt;/p&gt;
&lt;p&gt;In this post, we will explore the connection between the two by studying &lt;em&gt;mirror
descent&lt;/em&gt; and its information-geometric interpretation as natural gradient
in a dual space. But first, let&amp;#8217;s explore our two initial&amp;nbsp;ideas.&lt;/p&gt;
&lt;h3 id="reparametrization"&gt;Reparametrization.&lt;a class="headerlink" href="#reparametrization" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Instead of optimizing w.r.t. the constrained&amp;nbsp;variables &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt;, we introduce an
underlying&amp;nbsp;variable &lt;span class="arithmatex"&gt;\(u\)&lt;/span&gt;, such&amp;nbsp;that &lt;span class="arithmatex"&gt;\(x_i = \sigma(u_i)\)&lt;/span&gt;.&lt;label for="sn-5" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-5" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;This seems to be the
more common method among neural network practitioners; one
place where it shows up often is &lt;em&gt;neural variational inference&lt;/em&gt;, where we would
constrain the variance of a learned distribution using a &lt;em&gt;softplus&lt;/em&gt;
function.&lt;/span&gt;
In our case, we use a logistic sigmoid reparametrization to get the
unconstrained non-convex&amp;nbsp;problem&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \min_{u \in \reals^2} f(\sigma(u))\,, \]&lt;/div&gt;
&lt;p&gt;where &lt;span class="arithmatex"&gt;\(\sigma\)&lt;/span&gt; is applied element-wise.&lt;br /&gt;
When&amp;nbsp;reparametrizing, &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt; is no longer a learned parameter, but a function of
the learned&amp;nbsp;parameter &lt;span class="arithmatex"&gt;\(u\)&lt;/span&gt;. The gradient with respect&amp;nbsp;to &lt;span class="arithmatex"&gt;\(u\)&lt;/span&gt; can be handled
automatically by PyTorch&amp;nbsp;autodiff:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;optim_reparam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u_init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u_init&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_grad_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sigmoid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# compute grad wrt u&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;  &lt;span class="c1"&gt;# take gradient step&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With a very small learning rate, we get a glimpse into the dynamics of this
method.&lt;label for="sn-6" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-6" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;As the learning rate goes to zero, we are simulating a
continuous &lt;em&gt;gradient flow&lt;/em&gt;, of which gradient descent is a discretized
approximation. For more about gradient flows, check out &lt;a href="https://francisbach.com/gradient-flows/"&gt;Francis Bach&amp;#8217;s post&lt;/a&gt;.&lt;/span&gt;
For comparison, we include the unconstrained&amp;nbsp;trajectory.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Optimization trajectory of reparametrized gradient descent, learning rate 0.01." src="/images/mirror_primal_lr0.010_reparam.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;In practice, we would use a much larger learning rate to accelerate&amp;nbsp;optimization:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Optimization trajectory of reparametrized gradient descent, learning rate 0.2" src="/images/mirror_primal_lr0.200_reparam.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;We can see that even with a large learning rate, the reparametrization method
takes much smaller steps, especially as it gets closer to the boundary of the
domain. The steps are so small, that we only show the first 20 markers, to avoid
clutter. Why does this happen? At any&amp;nbsp;point &lt;span class="arithmatex"&gt;\(u\)&lt;/span&gt;, the reparametrized gradient can be written using the
chain&amp;nbsp;rule:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \pfrac{}{u} f(\sigma(u)) = 
\pfrac{\sigma(u)}{u} 
\pfrac{f(x)}{x}\biggr\rvert_{x=\sigma(u)} \]&lt;/div&gt;
&lt;p&gt;This is the unconstrained gradient&amp;nbsp;at &lt;span class="arithmatex"&gt;\(x=\sigma(u)\)&lt;/span&gt;, rescaled by the Jacobian&amp;nbsp;of &lt;span class="arithmatex"&gt;\(\sigma\)&lt;/span&gt;.&amp;nbsp;Since &lt;span class="arithmatex"&gt;\(\sigma\)&lt;/span&gt; acts elementwise, its Jacobian is a diagonal matrix,&amp;nbsp;with
&lt;span class="arithmatex"&gt;\(\pfrac{\sigma(u)_i}{u_i}  = \sigma(u_i)(1 - \sigma(u_i)) = x_i (1 - x_i).\)&lt;/span&gt; 
We can thus see that&amp;nbsp;as &lt;span class="arithmatex"&gt;\(x_i\)&lt;/span&gt; approaches &lt;span class="arithmatex"&gt;\(0\)&lt;/span&gt; or &lt;span class="arithmatex"&gt;\(1\)&lt;/span&gt;, the reparametrization rescales the
gradient &lt;strong&gt;severely&lt;/strong&gt;, bringing the effective step size toward 0.
It can help to see the concrete values and shapes of these&amp;nbsp;matrices:&lt;/p&gt;
&lt;details class="note"&gt;
&lt;summary&gt;Calculation&lt;/summary&gt;
&lt;p&gt;Let&amp;#8217;s consider two points: first far, then close to the optimum.
We use&amp;nbsp;that &lt;span class="arithmatex"&gt;\(\nabla_x f(x) = Q(x - x_0)\)&lt;/span&gt;.
(Note: all vectors below are column&amp;nbsp;vectors.)&lt;/p&gt;
&lt;p&gt;&lt;div class="wraptable"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;th&gt; &lt;span class="arithmatex"&gt;\(u\)&lt;/span&gt;
&lt;/th&gt;
&lt;th&gt; &lt;span class="arithmatex"&gt;\({x=\sigma(u)}\)&lt;/span&gt;
&lt;/th&gt;
&lt;th&gt; &lt;span class="arithmatex"&gt;\(\pfrac{\sigma(u)}{u}\)&lt;/span&gt; 
&lt;/th&gt;
&lt;th&gt; &lt;span class="arithmatex"&gt;\(\pfrac{f(x)}{x}\)&lt;/span&gt;
&lt;/th&gt;
&lt;th&gt; &lt;span class="arithmatex"&gt;\(\pfrac{f(\sigma(u))}{u}\)&lt;/span&gt; 
&lt;/th&gt;
&lt;/thead&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\((0, 0)\)&lt;/span&gt;       &lt;br /&gt;
&lt;/td&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\((.5, .5)\)&lt;/span&gt; 
&lt;/td&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\(\left(\begin{smallmatrix} .25 &amp;amp; 0 \\\\ 0 &amp;amp; .25 \end{smallmatrix}\right)\)&lt;/span&gt;
&lt;/td&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\((-2.2, -0.8)\)&lt;/span&gt;
&lt;/td&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\((-0.55, -0.2)\)&lt;/span&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\((4.6, -0.4)\)&lt;/span&gt;
&lt;/td&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\((.99, .4)\)&lt;/span&gt; 
&lt;/td&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\(\left(\begin{smallmatrix} .0099 &amp;amp; 0 \\\\ 0 &amp;amp; .24 \end{smallmatrix}\right)\)&lt;/span&gt;
&lt;/td&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\((-0.93, -0.12)\)&lt;/span&gt;
&lt;/td&gt;
&lt;td&gt;&lt;span class="arithmatex"&gt;\((-0.01, -0.03)\)&lt;/span&gt; 
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Two effects are at play here: the gradient&amp;nbsp;of &lt;span class="arithmatex"&gt;\(f\)&lt;/span&gt; gets smaller as we
approach the minimum, and the gradient&amp;nbsp;of &lt;span class="arithmatex"&gt;\(\sigma\)&lt;/span&gt; gets vanishingly small as
we approach the boundary of the&amp;nbsp;domain.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Remember, this rescaling
happens &lt;em&gt;automatically&lt;/em&gt; through the chain rule! But, although slowly, and along a
slightly winding trajectory, our method finds the right&amp;nbsp;answer.&lt;/p&gt;
&lt;h3 id="projected-gradient"&gt;Projected gradient.&lt;a class="headerlink" href="#projected-gradient" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The projected gradient method is particularly well suited to handling &amp;#8220;simple&amp;#8221; constraints
like the box case, but, unlike reparametrization, requires a different kind of
expertise to get running in the case of complicated constraints.&lt;label for="sn-7" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-7" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;&lt;span class="caps"&gt;PG&lt;/span&gt;
is very popular in convex optimization, useful both for theory and for
practice. However, it does not seem to be so widely used in the pure neural
network world, perhaps mostly because it is not directly supported by the
built-in optimizers in major frameworks.&lt;/span&gt;
For box constraints, the projection can be computed efficiently,&amp;nbsp;since
&lt;span class="arithmatex"&gt;\(\big[\!\operatorname{Proj}_{[0,1]^d}(x)\big]_i = \operatorname{clip}_{[0,1]}(x_i).\)&lt;/span&gt;
The implementation&amp;nbsp;follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;optim_pg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x_init&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_grad_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;  &lt;span class="c1"&gt;# take gradient step&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# project&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;#8217;s visualize the trajectory. From now on, we will zoom in a bit on the region
of&amp;nbsp;interest.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Optimization trajectory of projected gradient, learning rate=0.01" src="/images/mirror_primal_lr0.010_pg.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;It looks like the projected gradient method tends to follow the unconstrained
trajectory while sticking to the boundary of the domain. Of course, with larger
steps, the differences become more&amp;nbsp;pronounced.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Optimization trajectory of projected gradient, learning rate=0.2" src="/images/mirror_primal_lr0.200_pg.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;In this instance, &lt;span class="caps"&gt;PG&lt;/span&gt; is the clear winner: look how fast it makes progress. With
less well-behaved and non-convex functions this need not be the case. So we are
motivated to delve deeper and explore how &lt;span class="caps"&gt;PG&lt;/span&gt; and &lt;span class="caps"&gt;REP&lt;/span&gt; relate, despite seeming so&amp;nbsp;different. &lt;/p&gt;
&lt;h1 id="generalizing-the-projected-gradient-method-with-divergences"&gt;Generalizing the projected gradient method with divergences.&lt;a class="headerlink" href="#generalizing-the-projected-gradient-method-with-divergences" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;In the projected gradient method, we take unconstrained steps, which might take
us outside&amp;nbsp;of &lt;span class="arithmatex"&gt;\(\mathcal{X}\)&lt;/span&gt;, and then move the solution back&amp;nbsp;to &lt;span class="arithmatex"&gt;\(\mathcal{X}\)&lt;/span&gt; by&amp;nbsp;projection:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ x^{(t+1)} \leftarrow \operatorname{Proj}_\mathcal{X}\big(x^{(t+½)}\big)\,. \]&lt;/div&gt;
&lt;p&gt;Projection finds the&amp;nbsp;point &lt;span class="arithmatex"&gt;\(x \in \mathcal{X}\)&lt;/span&gt; closest&amp;nbsp;to &lt;span class="arithmatex"&gt;\(x^{(t+½)}\)&lt;/span&gt;,&amp;nbsp;i.e.,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \operatorname{Proj}_\mathcal{X}\big(x^{(t+½)}\big) \coloneqq \argmin_{x \in \mathcal{X}} \| x - x^{(t+½)} \|^2\,. \]&lt;/div&gt;
&lt;p&gt;The projected gradient update can be interpreted as minimizing a regularized linearization&amp;nbsp;of
&lt;span class="arithmatex"&gt;\(f\)&lt;/span&gt; around the current&amp;nbsp;iterate,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ x^{(t+1)} \leftarrow \argmin_{x \in \mathcal{X}}  \DP{\nabla f(x^{(t)})}{x} + 
{\frac{1}{2\alpha_t}\|x - x^{(t)}\|^2}\,.
\]&lt;/div&gt;
&lt;details class="note"&gt;
&lt;summary&gt;Explanation&lt;/summary&gt;
&lt;p&gt;Why does this update make sense, and where does it come from? We are trying to
minimize a&amp;nbsp;function &lt;span class="arithmatex"&gt;\(f(x)\)&lt;/span&gt;, but we don&amp;#8217;t know what it looks like globally: we only
have access to its&amp;nbsp;value &lt;span class="arithmatex"&gt;\(f(x)\)&lt;/span&gt; and its&amp;nbsp;gradient &lt;span class="arithmatex"&gt;\(\nabla f(x)\)&lt;/span&gt; at&amp;nbsp;points &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt;
that we may query one at a time. At any&amp;nbsp;point &lt;span class="arithmatex"&gt;\(x_0\)&lt;/span&gt;,
the first-order Taylor expansion&amp;nbsp;is:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ f(x_0 + \delta) = f(x_0) + \DP{\nabla f(x_0)}{\delta} + o(\|\delta\|)\,. \]&lt;/div&gt;
&lt;p&gt;To get a linear approximation&amp;nbsp;of &lt;span class="arithmatex"&gt;\(f\)&lt;/span&gt; we can plug&amp;nbsp;in &lt;span class="arithmatex"&gt;\(\delta = x - x_0\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ f(x) =  f(x_0) + \DP{\nabla f(x_0)}{x - x_0} + o(\|x - x_0\|)\,. \]&lt;/div&gt;
&lt;p&gt;So as long&amp;nbsp;as &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt; is not too far&amp;nbsp;from &lt;span class="arithmatex"&gt;\(x_0\)&lt;/span&gt;, we&amp;nbsp;have &lt;/p&gt;
&lt;div class="arithmatex"&gt;\[f(x) \approx \tilde{f}_{x_0}(x) \coloneqq f(x_0) + \DP{\nabla f(x_0)}{x - x_0}\,.\]&lt;/div&gt;
&lt;p&gt;This affine approximation is much easier to
minimize, but it is only accurate locally, therefore, we use it iteratively,
taking a small step, then updating the&amp;nbsp;approximation:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ 
x^{(t+1)} \leftarrow \argmin_{x \in \mathcal{X}} \tilde f_{x^{(t)}}(x) + \frac{1}{2\alpha_t}\|x - x^{(t)}\|^2\,. 
\]&lt;/div&gt;
&lt;p&gt;where the term on the right keeps us close&amp;nbsp;to &lt;span class="arithmatex"&gt;\(x^{(t)}\)&lt;/span&gt; to ensure the
approximation is not too bad. Clearing up the constant terms from inside&amp;nbsp;the
&lt;span class="arithmatex"&gt;\(\argmin\)&lt;/span&gt; yields the desired&amp;nbsp;expression.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Rearranging the terms reveals exactly the projected gradient&amp;nbsp;update,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ x^{(t+1)} \leftarrow \operatorname{Proj}_{\mathcal{X}}\big(
x^{(t)} - \alpha_t \nabla f(x^{(t)})\big)\,.\]&lt;/div&gt;
&lt;details class="note"&gt;
&lt;summary&gt;Derivation&lt;/summary&gt;
&lt;div class="arithmatex"&gt;\[
\begin{aligned}
 &amp;amp; \argmin_{x \in \mathcal{X}} \DP{x}{\nabla f(x^{(t)})} + \frac{1}{2\alpha_t} \|x-x^{(t)}\|^2 \\
=&amp;amp; \argmin_{x \in \mathcal{X}} \DP{x}{\nabla f(x^{(t)})} + \frac{1}{2\alpha_t} \|x\|^2 - \frac{1}{\alpha_t} \DP{x}{x^{(t)}} \\
=&amp;amp; \argmin_{x \in \mathcal{X}} \alpha_t \DP{x}{\nabla f(x^{(t)})} + \frac{1}{2} \|x\|^2 - \DP{x}{x^{(t)}} \\
=&amp;amp; \argmin_{x \in \mathcal{X}} \DP{x}{\underbrace{\alpha_t \nabla f(x^{(t)}) - x^{(t)}}_{-x^{(t+½)}}} + \frac{1}{2} \|x\|^2 \\
=&amp;amp; \argmin_{x \in \mathcal{X}} \frac{1}{2} \| x - x^{(t+½)} \|^2 \textcolor{gray}{ - \frac{1}{2} \|x^{(t+½)}\|} \\
=&amp;amp; \operatorname{Proj}_{\mathcal{X}} (x^{(t+½)})\,.
\end{aligned}
\]&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Now, let&amp;#8217;s pay special attention to 
the&amp;nbsp;function &lt;span class="arithmatex"&gt;\(D(x, y) = \| x - y \|^2 = \sum_i (x_i - y_i)^2\)&lt;/span&gt;, the &lt;em&gt;squared Euclidean distance&lt;/em&gt;.
We employ this function to ensure that each update stays close&amp;nbsp;to &lt;span class="arithmatex"&gt;\(x^{(t)}\)&lt;/span&gt;,
because the linear approximation is not good if we go to far. But this is not
the only good measure of closeness:
here, geometry enters the stage!  Euclidean geometry is
convenient and comfortable for thinking about spaces&amp;nbsp;like &lt;span class="arithmatex"&gt;\(\reals^d,\)&lt;/span&gt; but
it is not always the best&amp;nbsp;model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bregman divergences&lt;/strong&gt;
provide a convenient generalization of the squared
Euclidean distance:&lt;label for="sn-8" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-8" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;
Divergences measures of dissimilarity between objects, with weaker requirements
than distance functions: simply&amp;nbsp;that &lt;span class="arithmatex"&gt;\(D(x,y) \geq 0\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\(D(x,y)=0\)&lt;/span&gt; iff. &lt;span class="arithmatex"&gt;\(x =
y\)&lt;/span&gt;. &lt;a href="https://en.wikipedia.org/wiki/Bregman_divergence"&gt;Bregman divergences&lt;/a&gt; 
are an important class of divergences. They are convex in the first argument, but not the second.
&lt;/span&gt;
given strictly convex,&amp;nbsp;twice-differentiable &lt;span class="arithmatex"&gt;\(\Psi\)&lt;/span&gt;,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ D_\Psi(x, y) \coloneqq \Psi(x) - \Psi(y) - \DP{\nabla \Psi(y)}{x - y}\,. \]&lt;/div&gt;
&lt;p&gt;For &lt;span class="arithmatex"&gt;\(\Psi = \frac{1}{2} \| \cdot \|^2\)&lt;/span&gt;, this recovers the squared Euclidean
distance.&amp;nbsp;Replacing &lt;span class="arithmatex"&gt;\(\frac{1}{2}\|\cdot\|^2\)&lt;/span&gt; by &lt;span class="arithmatex"&gt;\(D_\Psi\)&lt;/span&gt; in the projected
gradient algorithm leads to a generalization known as &lt;strong&gt;mirror descent&lt;/strong&gt;,&lt;label for="sn-9" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-9" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;
Introduced in: &lt;br&gt;
&lt;span class="caps"&gt;A.S.&lt;/span&gt; Nemirovsky and &lt;span class="caps"&gt;D.B.&lt;/span&gt; Yudin,, 1983. Problem complexity and method efficiency in optimization.
&lt;br&gt;Suggested reading: 
A. Beck and M. Teboulle, 2003. 
&lt;a href="https://web.iem.technion.ac.il/images/user-files/becka/papers/3.pdf"&gt;Mirror descent and nonlinear projected subgradient methods for convex optimization.&lt;/a&gt; 
&lt;em&gt;Operations Research Letters, 31(3).&lt;/em&gt; 167-175.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[
x^{(t+1)} \leftarrow \argmin_{x \in \mathcal{X}}  \DP{\nabla f(x^{(t)})}{x} + 
{\frac{1}{\alpha_t}D_\Psi(x, x^{(t)})}\,.
\]&lt;/div&gt;
&lt;p&gt;Since &lt;span class="arithmatex"&gt;\(\Psi\)&lt;/span&gt; is twice differentiable and strongly convex, it has a&amp;nbsp;gradient
&lt;span class="arithmatex"&gt;\(\psi = \nabla\Psi\)&lt;/span&gt; which is invertible. Solving for the mirror descent update
yields a so-called Bregman projection, or &lt;em&gt;non-linear&lt;/em&gt;&amp;nbsp;projection,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[
\begin{aligned}
u^{(t+½)} &amp;amp;\leftarrow \psi(x^{(t)}) - \alpha_t \nabla f(x^{(t)})\,, \\ 
x^{(t+1)} &amp;amp;\leftarrow 
\argmin_{x \in \mathcal{X}} D_\Psi\big(x,  \psi^{-1}(u^{(t+½)})\big)\,. \\
\end{aligned}
\]&lt;/div&gt;
&lt;p&gt;If &lt;span class="arithmatex"&gt;\(\Psi\)&lt;/span&gt; is carefully chosen such&amp;nbsp;that &lt;span class="arithmatex"&gt;\(\psi^{-1}(u) \in \mathcal{X}\)&lt;/span&gt; for&amp;nbsp;all
&lt;span class="arithmatex"&gt;\(u \in \mathcal{U}\)&lt;/span&gt;, then the Bregman projection step is trivial,&amp;nbsp;yielding&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ x^{(t+1)} \leftarrow \psi^{-1}\big(
\psi(x^{(t)}) - \alpha_t \nabla f(x^{(t)})
\big)\,. \]&lt;/div&gt;
&lt;p&gt;Let&amp;#8217;s make this more&amp;nbsp;concrete.&lt;/p&gt;
&lt;p&gt;The choice&amp;nbsp;of &lt;span class="arithmatex"&gt;\(\Psi\)&lt;/span&gt; will define the &lt;em&gt;geometry&lt;/em&gt; of our space.
Values&amp;nbsp;in &lt;span class="arithmatex"&gt;\([0,1]\)&lt;/span&gt; may be interpreted as &lt;em&gt;coin flip&lt;/em&gt; probabilities:
the higher, the more likely an event is to happen. An important property of a
binary random variable is its entropy.&amp;nbsp;If &lt;span class="arithmatex"&gt;\(x_i \in [0, 1]\)&lt;/span&gt; denotes the
probability associated with&amp;nbsp;coin &lt;span class="arithmatex"&gt;\(i\)&lt;/span&gt;, the entropy&amp;nbsp;is&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[H(x_i) = -x_i \log x_i - (1 - x_i) \log (1 - x_i)\,.\]&lt;/div&gt;
&lt;p&gt;We may extend this additively to vectors&amp;nbsp;as &lt;span class="arithmatex"&gt;\(H(x) = \sum_i H(x_i)\)&lt;/span&gt;.
This is sometimes called the Fermi-Dirac entropy.&amp;nbsp;On &lt;span class="arithmatex"&gt;\(\mathcal{X}=[0, 1]\)&lt;/span&gt;, entropy is continuously differentiable and strictly
&lt;strong&gt;concave&lt;/strong&gt;, maximized&amp;nbsp;at &lt;span class="arithmatex"&gt;\(x=0.5\)&lt;/span&gt; and minimized&amp;nbsp;at &lt;span class="arithmatex"&gt;\(x=0\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\(x=1\)&lt;/span&gt;.&lt;label for="sn-10" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-10" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;&lt;span class="arithmatex"&gt;\(H(0.5) =
\log 2,\)&lt;/span&gt;&lt;br&gt;&lt;span class="arithmatex"&gt;\(H(0)=H(1)=0\)&lt;/span&gt;.&lt;/span&gt; &lt;/p&gt;
&lt;p&gt;Let us thus&amp;nbsp;take &lt;span class="arithmatex"&gt;\(\Psi = -H\)&lt;/span&gt;. Its gradient&amp;nbsp;is &lt;span class="arithmatex"&gt;\(\psi : \mathcal{X} \rightarrow \mathcal{U}\)&lt;/span&gt;,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \psi(x) \coloneqq -\nabla H(x) = \log(x) - \log(1-x)\,, \]&lt;/div&gt;
&lt;p&gt;with&amp;nbsp;inverse &lt;span class="arithmatex"&gt;\(\phi : \mathcal{U} \rightarrow \mathcal{X}\)&lt;/span&gt;,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \phi(u) \coloneqq \psi^{-1}(u) = \frac{1}{1 + \exp(-u)} = \sigma(u)\,. \]&lt;/div&gt;
&lt;!--
The entropy induces a Bregman divergence $D_{-H}$, which after some manipulation
can be written as

$$D_{-H}(x, y) = x \log \frac{x}{y} - (1-x) \log \frac{1-x}{1-y}. $$
--&gt;

&lt;p&gt;So, written in terms of the familiar logistic sigmoid, the mirror descent update induced by the negative entropy takes the (remarkable!)&amp;nbsp;form&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ x^{(t+1)} = \sigma(\sigma^{-1}(x^{(t)}) - \alpha_t \nabla f(x^{(t)}))\,. \]&lt;/div&gt;
&lt;p&gt;Things are beginning to clear up! We can think of the pair of inverse&amp;nbsp;functions
&lt;span class="arithmatex"&gt;\((\psi, \phi)\)&lt;/span&gt; as maps&amp;nbsp;between &lt;span class="arithmatex"&gt;\(\mathcal{X}\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\(\mathcal{U}\)&lt;/span&gt;.  We will call these
the &lt;strong&gt;primal&lt;/strong&gt; and &lt;strong&gt;dual&lt;/strong&gt; spaces, respectively.&lt;label for="sn-11" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-11" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;Duality, and the terms
&lt;em&gt;primal&lt;/em&gt; and &lt;em&gt;dual&lt;/em&gt;, are fairly strong, scary, and sometimes
overloaded. 
Readers familiar with the Fenchel&amp;nbsp;conjugate,
&lt;span class="arithmatex"&gt;\(\Psi^*(u) \coloneqq {\sup_{x} \{ \DP{u}{x} - \Psi(x) \},}\)&lt;/span&gt;
should note 
note&amp;nbsp;that &lt;span class="arithmatex"&gt;\(\Psi^* = \Phi\)&lt;/span&gt;,&amp;nbsp;where &lt;span class="arithmatex"&gt;\(\Phi\)&lt;/span&gt; is such&amp;nbsp;that
&lt;span class="arithmatex"&gt;\(\nabla\Phi = \phi\)&lt;/span&gt;. 
(For the Fermi-Dirac&amp;nbsp;entropy, &lt;span class="arithmatex"&gt;\(\Phi(u) =
{\log(1+\exp(u)).}\)&lt;/span&gt;) 
The dual construction used here relies on the relationship
between Bregman divergences and Fenchel&amp;nbsp;conjugates:
&lt;span class="arithmatex"&gt;\( D_{\Phi^*}(u, v) = D_\Phi(y, x)\)&lt;/span&gt; where
&lt;span class="arithmatex"&gt;\(u=\psi(x), v=\psi(y)\)&lt;/span&gt; are the dual points corresponding&amp;nbsp;to &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\(y\)&lt;/span&gt;.
&lt;/span&gt;
Mirror descent thus
first moves into dual (unconstrained) space, performs an update there, and then moves
back. Reparametrization rewrites the problem in dual coordinates and performs
gradient descent: this is not the same, and the trajectories are quite&amp;nbsp;different!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Optimization trajectory of mirror descent, learning rate 0.01" src="/images/mirror_primal_lr0.010_md.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;With a larger step size, we see that mirror descent takes much larger steps than
reparametrization&amp;nbsp;does.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Optimization trajectory of mirror descent, learning rate 0.2" src="/images/mirror_primal_lr0.200_md.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;Now that we figured out that we may think about the problem in primal or dual
coordinates, we may also visualize the optimization trajectory in dual&amp;nbsp;coordinates.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dual coordinate trajectory of mirror descent, learning rate 0.01" src="/images/mirror_dual_lr0.010_md.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;Yet, the way that mirror descent leans on moving&amp;nbsp;from &lt;span class="arithmatex"&gt;\(\mathcal{X}\)&lt;/span&gt; to
&lt;span class="arithmatex"&gt;\(\mathcal{U}\)&lt;/span&gt; is very familiar to the reparametrization strategy. Is there a
deeper connection between the two? We illuminate it&amp;nbsp;next.&lt;/p&gt;
&lt;h1 id="duality-between-mirror-descent-and-natural-gradient"&gt;Duality between mirror descent and natural&amp;nbsp;gradient.&lt;a class="headerlink" href="#duality-between-mirror-descent-and-natural-gradient" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;We have seen that different choices&amp;nbsp;of &lt;span class="arithmatex"&gt;\(\Psi\)&lt;/span&gt; induce different geometries
even on top of the same space. (Case in point: entropy&amp;nbsp;vs. &lt;span class="arithmatex"&gt;\(\frac{1}{2}\|\cdot\|^2\)&lt;/span&gt;).
To handle this ambiguity, we need a structure that attaches the geometry along
with the underlying space. This, (with some handwaving), is a &lt;em&gt;Riemannian
manifold&lt;/em&gt;: a&amp;nbsp;pair &lt;span class="arithmatex"&gt;\((\mathcal{U}, G)\)&lt;/span&gt; where &lt;span class="arithmatex"&gt;\(\mathcal{U} \subseteq \reals^d\)&lt;/span&gt;
is an underlying space&lt;label for="sn-12" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-12" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;Riemannian manifolds are more general than this, but
in this post, for simplicity, we only look at the case&amp;nbsp;where &lt;span class="arithmatex"&gt;\(\mathcal{U}\subseteq \reals^d\)&lt;/span&gt;.
To be fully general, the notation ramps up quickly.
See &lt;a href="https://wiseodd.github.io/techblog/2019/02/22/riemannian-geometry/"&gt;Agustinus Kristiadi&amp;#8217;s
post&lt;/a&gt; 
and &lt;a href="https://arxiv.org/abs/1808.08271"&gt;Frank Nielsen&amp;#8217;s tutorial&lt;/a&gt;
for an introduction.
&lt;/span&gt;&amp;nbsp;and &lt;span class="arithmatex"&gt;\(G: \mathcal{U} \rightarrow \reals^{d \times d},\)&lt;/span&gt;
known as the &lt;em&gt;metric tensor&lt;/em&gt;, is a function that takes a&amp;nbsp;point &lt;span class="arithmatex"&gt;\(u_0\)&lt;/span&gt;
on the manifold
and returns a positive definite matrix that characterizes the &lt;strong&gt;geometry&lt;/strong&gt; by specifying a custom squared distance&amp;nbsp;nearby &lt;span class="arithmatex"&gt;\(u_0\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ d^2_{u_0}(u, v) = \frac{1}{2} (u-v)^\top G(u_0) (u - v)\,. \]&lt;/div&gt;
&lt;p&gt;What we observed earlier is in fact a duality between the Riemannian&amp;nbsp;manifolds
&lt;span class="arithmatex"&gt;\((\mathcal{X}, \nabla^2 \Psi)\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\((\mathcal{U}, \nabla^2 \Phi),\)&lt;/span&gt; 
where &lt;span class="arithmatex"&gt;\(\Phi\)&lt;/span&gt; is the antiderivative&amp;nbsp;of &lt;span class="arithmatex"&gt;\(\phi\)&lt;/span&gt;,&amp;nbsp;i.e., &lt;span class="arithmatex"&gt;\(\nabla\Phi=\phi\)&lt;/span&gt;.
This is an important duality studied in the field of information geometry.&lt;label for="sn-13" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-13" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;
S. Amari, A. Cichocki. 2010. 
&lt;a href="http://fluid.ippt.gov.pl/bulletin/(58-1)183.pdf"&gt;Information geometry of divergence
functions.&lt;/a&gt;
Bulletin of the Polish Academy of Sciences, 58(1).&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We may now revisit the reparametrization strategy, in light of what we learned
so far. To reparametrize the constraint away, we work in &lt;strong&gt;dual&amp;nbsp;coordinates&lt;/strong&gt;
&lt;span class="arithmatex"&gt;\(u \in \mathcal{U}\)&lt;/span&gt;, and&amp;nbsp;update:&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ u^{(t+1)} \leftarrow u^{(t)} - \nabla_u f(\phi(u))\,. \]&lt;/div&gt;
&lt;p&gt;In our&amp;nbsp;case &lt;span class="arithmatex"&gt;\(\mathcal{U}=\reals^2\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\(\phi = \sigma\)&lt;/span&gt;, but let&amp;#8217;s use the more
general notation. The update above ignores the geometry&amp;nbsp;of &lt;span class="arithmatex"&gt;\(\mathcal{U}\)&lt;/span&gt;, in
other words, operates on the trivial&amp;nbsp;manifold &lt;span class="arithmatex"&gt;\((\mathcal{U}, I_d)\)&lt;/span&gt; &amp;#8212; the
metric tensor is the identity matrix everywhere.  When optimizing some&amp;nbsp;function &lt;span class="arithmatex"&gt;\(\tilde{f}(u)\)&lt;/span&gt;
over a Riemannian&amp;nbsp;manifold &lt;span class="arithmatex"&gt;\((\mathcal{U}, G)\)&lt;/span&gt;,
a convenient algorithm is natural gradient,&lt;label for="sn-14" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-14" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;S. Amari, 1998.&lt;br /&gt;
&lt;a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.452.7280&amp;amp;rep=rep1&amp;amp;type=pdf"&gt;Natural gradient works efficiently in learning.&lt;/a&gt;
&lt;em&gt;Neural computation 10.2&lt;/em&gt;, 251-276.&lt;/span&gt;
which takes the&amp;nbsp;form&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ u^{(t+1)} \leftarrow u^{(t)} - \alpha_t [G(u)]^{-1} \nabla \tilde{f}(u^{(t)})\,. \]&lt;/div&gt;
&lt;details class="note"&gt;
&lt;summary&gt;Derivation&lt;/summary&gt;
&lt;p&gt;We follow the same steps as for gradient descent, but we use the induced&amp;nbsp;distance
&lt;span class="arithmatex"&gt;\(d_{u^{(t)}}\)&lt;/span&gt; to find an update direction. We must solve the&amp;nbsp;problem&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \argmin_{u\in\mathcal{U}} \DP{u}{\nabla \tilde{f}(u^{(t)})} + \frac{1}{2\alpha_t} 
{(u - u^{(t)})^\top G(u^{(t)}) (u - u^{(t)})}\, \]&lt;/div&gt;
&lt;p&gt;Rearranging gives the equivalent&amp;nbsp;problem&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \argmin_{u\in\mathcal{U}} d^2_{u^{(t)}}
(u, u^{(t)} - \alpha_t[G(u^{(t)})]^{-1} \nabla \tilde{f}(u^{(t)}))\,.\]&lt;/div&gt;
&lt;p&gt;Assuming no constraints&amp;nbsp;(i.e., &lt;span class="arithmatex"&gt;\(\mathcal{U}=\reals^d\)&lt;/span&gt;), this yields the
desired&amp;nbsp;update.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;It turns out that for a pair of Bregman dual&amp;nbsp;manifolds
&lt;span class="arithmatex"&gt;\((\mathcal{X}, \nabla^2 \Psi)\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\((\mathcal{U}, \nabla^2 \Phi),\)&lt;/span&gt; 
&lt;strong&gt;mirror descent in the primal space is equivalent to natural gradient in the
dual space!&lt;/strong&gt; This result, due to Raskutti and Mukherjee (2015),&lt;label for="sn-15" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-15" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;
G. Raskutti and S. Mukherjee. 2015. &lt;em&gt;The information geometry of mirror descent.&lt;/em&gt;
In: &lt;span class="caps"&gt;IEEE&lt;/span&gt; Transactions on Information Theory, vol. 61, issue 3.
&lt;a href="https://arxiv.org/abs/1310.7780"&gt;arXiv:1310.7780&lt;/a&gt;.&lt;/span&gt;
means we can get a geometry-aware flavor of the
&lt;span class="caps"&gt;REP&lt;/span&gt;&amp;nbsp;algorithm,&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ u^{(t+1)} \leftarrow u^{(t)} - \alpha_t [\nabla^2 \Phi(u)]^{-1} \nabla_u {f}(\phi(u^{(t)}))\,. \]&lt;/div&gt;
&lt;p&gt;This algorithm produces the exact same iterates as mirror descent, but &amp;#8212; since
it maintains iterates in dual space &amp;#8212; is more numerically stable.
A first attempt at implementing natural gradient&amp;nbsp;is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;optim_rep_natural&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u_init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u_init&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_grad_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sigmoid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;dsigmoid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;detach&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# diagonal of ∇²Φ(u)&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;dsigmoid&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and we can visually check that we get exactly the same trajectory as mirror&amp;nbsp;descent.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Optimization trajectories. Natural gradient matches exactly mirror
descent." src="/images/mirror_primal_lr0.010_nat.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;But let&amp;#8217;s look a bit closer!&amp;nbsp;Since &lt;span class="arithmatex"&gt;\(\phi = \nabla\Phi\)&lt;/span&gt;, our metric tensor is
none other&amp;nbsp;than&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ \nabla^2\Phi(u) = \pfrac{\phi(u)}{u}\,, \]&lt;/div&gt;
&lt;p&gt;and, putting the whole update together, we see&amp;nbsp;that&lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ [\nabla^2\Phi(u)]^{-1} \nabla_u f(\phi(u)) = 
{\left(\pfrac{\phi(u)}{u}\right)^{-1}}
{\pfrac{\phi(u)}{u}}
{\pfrac{f(x)}{x}\biggr\rvert_{x=\phi(u)}} \]&lt;/div&gt;
&lt;p&gt;Natural gradient cancels out the Jacobian&amp;nbsp;of &lt;span class="arithmatex"&gt;\(\phi\)&lt;/span&gt; in the chain rule!
This suggests an alternative implementation,
reminiscent of &lt;em&gt;straight-through&lt;/em&gt; tricks: we use a reparametrization&amp;nbsp;function
&lt;span class="arithmatex"&gt;\(\sigma\)&lt;/span&gt; that is a sigmoid in the forward pass, but acts as if it were the
identity in the backward pass.&lt;label for="sn-16" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-16" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;
Such &amp;#8220;straight-through&amp;#8221; heuristics are very popular for dealing with
stochastic or piecewise-constant functions (like &lt;em&gt;argmax&lt;/em&gt; or &lt;em&gt;floor&lt;/em&gt; functions.)
To my knowledge, the idea was introduced by G. Hinton in the Neural Networks for
Machine Learning online lectures in 2012. For a detailed treatment, see:&lt;br&gt;
Y. Bengio, N. Léonard, A Courville. 2013. 
Estimating or Propagating Gradients Through Stochastic Neurons for Conditional Computation.
&lt;a href="https://arxiv.org/abs/1308.3432"&gt;&lt;em&gt;arXiv:1308.3432.&lt;/em&gt;&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SigmoidStraightThrough&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autograd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sigmoid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sigmoid_straight_through&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SigmoidStraightThrough&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;optim_rep_natural_st&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u_init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u_init&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requires_grad_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigmoid_straight_through&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grad&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero_&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sigmoid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With&amp;nbsp;this &lt;code&gt;sigmoid_straight_through&lt;/code&gt; nonlinearity, we can now use mirror descent
/ natural gradient to learn constrained parameters without any changes to the
optimizer, and with improved numerical stability.&lt;label for="sn-17" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-17" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;Instead of cancelling out
the gradient&amp;nbsp;of &lt;span class="arithmatex"&gt;\(\sigma\)&lt;/span&gt; numerically, we now avoid multiplying by it in the
first&amp;nbsp;place.&lt;/span&gt; Unlike our initial reparametrization algorithm, this algorithm uses the
geometry of the dual&amp;nbsp;space &lt;span class="arithmatex"&gt;\((\mathcal{U}, \nabla^2\Phi)\)&lt;/span&gt; and thus avoids taking
extra small steps while still ensuring that all the iterates remain&amp;nbsp;feasible.&lt;/p&gt;
&lt;h1 id="conclusions"&gt;Conclusions.&lt;a class="headerlink" href="#conclusions" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;We have explored the two most popular strategies for dealing with simple
constraints: reparametrization and projected gradient optimization. We have
looked into an information geometric generalization of projected gradient, which
turns out to lead to an equivalent &lt;em&gt;dual&lt;/em&gt; algorithm that resembles
reparametrization, but with a gradient correction that improves its performance.
(And,&amp;nbsp;if &lt;span class="arithmatex"&gt;\(f\)&lt;/span&gt; is convex, results in a convex optimization procedure, unlike the
reparametrized&amp;nbsp;case.)&lt;/p&gt;
&lt;p&gt;Of course, we only looked at a simple quadratic test case, and we did not check
what happens when using accelerated methods or adaptive learning rates (e.g,.
Adam). But empirical questions are not the main&amp;nbsp;point. &lt;/p&gt;
&lt;p&gt;With this post, I have hopefully stirred your interest into constrained
optimization and its connections to geometry. 
You may be wondering what other constraints we may handle this way.
Two important examples are non-negativity&amp;nbsp;constraints &lt;span class="arithmatex"&gt;\(\mathcal{X}=[0,
\infty)^d,\)&lt;/span&gt; for which we may&amp;nbsp;use 
&lt;span class="arithmatex"&gt;\(\Psi(x) = \sum_i x_i(\log x_i - 1),\)&lt;/span&gt; giving &lt;span class="arithmatex"&gt;\(\phi(u) = \exp(u)\)&lt;/span&gt;,&lt;label for="sn-18" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-18" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;Another
interesting choice&amp;nbsp;is &lt;span class="arithmatex"&gt;\(\Psi(x) = x \log(\exp(x)-1) +
\operatorname{Li}_2(1-\exp(x)),\)&lt;/span&gt; where &lt;span class="arithmatex"&gt;\(\operatorname{Li}_2\)&lt;/span&gt;
denotes the &lt;a href="https://en.wikipedia.org/wiki/Spence%27s_function"&gt;dilogarithm&lt;/a&gt;.
This leads to the softplus&amp;nbsp;reparametrization
&lt;span class="arithmatex"&gt;\(\phi(u) = \log(1+\exp(u))\)&lt;/span&gt;. While the dilogarithm lacks a closed-form
expression, we only need to&amp;nbsp;evaluate &lt;span class="arithmatex"&gt;\(\phi\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\(\psi\)&lt;/span&gt;.&lt;/span&gt;
and the&amp;nbsp;simplex &lt;span class="arithmatex"&gt;\(\mathcal{X} = {\{ x \in \reals^d : x_i \geq 0, \sum_i x_i = 1
\},}\)&lt;/span&gt; for which the negative Shannon&amp;nbsp;entropy &lt;span class="arithmatex"&gt;\(\Psi(x) = {\sum_i x_i \log x_i}\)&lt;/span&gt;
yields the softmax&amp;nbsp;reparametrization &lt;span class="arithmatex"&gt;\(\phi(u) = {\frac{1}{Z} \exp(u)}\)&lt;/span&gt; with &lt;span class="arithmatex"&gt;\(Z=\sum_i\exp(u_i).\)&lt;/span&gt;
The resulting simplex-constrained algorithm is known under many names, including
&amp;#8220;multiplicative weights&amp;#8221;, &amp;#8220;entropic descent&amp;#8221;, or &amp;#8220;exponentiated gradient&amp;#8221;.&lt;label for="sn-19" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-19" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;
J. Kivinen, J and &lt;span class="caps"&gt;M.K.&lt;/span&gt; Warmuth, 1997. 
&lt;a href="https://users.soe.ucsc.edu/~manfred/pubs/J36.pdf"&gt;Exponentiated gradient versus gradient descent for linear predictors.&lt;/a&gt;
&lt;em&gt;Information and Computation, 132(1),&lt;/em&gt; 1-63.
&lt;/span&gt; This algorithm performs the elementwise multiplicative&amp;nbsp;update &lt;/p&gt;
&lt;div class="arithmatex"&gt;\[ x^{(t+1)} \propto x^{(t)} \odot \exp\big(-\alpha^{(t)} \nabla f(x^{(t)})\big)\,.\]&lt;/div&gt;
&lt;p&gt;More interesting constraint examples involve matrices.
Geometric insights have been key to advances in learning over constrained spaces of
matrices, such as symmetric, low-rank, orthonormal, positive (semi-)definite matrices, etc.&lt;label for="sn-20" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-20" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;
P.-A. Absil, R. Mahoney, and Rodolphe Sepulchre. 2008.
&lt;a href="https://press.princeton.edu/absil"&gt;Optimization Algorithms on Matrix
Manifolds&lt;/a&gt;.
Princeton University Press. &lt;span class="caps"&gt;ISBN&lt;/span&gt; 978-0-691-13298-3
&lt;/span&gt; 
Schäfer et al. use the duality between mirror descent and natural gradient to derive
powerful algorithms for minimax problems.&lt;label for="sn-21" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-21" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;
F. Schäfer, A. Anandkumar, H. Owhadi. 2020.
Competitive Mirror Descent.
&lt;a href="https://arxiv.org/abs/2006.10179"&gt;&lt;em&gt;arXiv:2006.10179&lt;/em&gt;&lt;/a&gt;.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1 id="acknowledgements"&gt;Acknowledgements.&lt;a class="headerlink" href="#acknowledgements" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This post was inspired by &lt;a href="https://video.ias.edu/machinelearning/2020/0709-AnimaAnandkumar"&gt;Anima Anandkumar&amp;#8217;s
talk&lt;/a&gt; at the 
&lt;span class="caps"&gt;IAS&lt;/span&gt; Seminar on Theoretical Machine Learning. Before this talk, I had no idea
about anything in the second part of this post.
Thanks to Mathieu Blondel, Caio Corro, André Martins, Fabian Pedregosa, and Justine Zhang
for feedback, and to all Twitter users who pointed out typos.
I am funded by the European Research Council
StG DeepSPIN 758969 and Fundação para a Ciência e Tecnologia contract &lt;span class="caps"&gt;UIDB&lt;/span&gt;/50008/2020. &lt;/p&gt;</content><category term="optimization"/></entry><entry><title>Rendering 3-d surface plots for social media</title><link href="//vene.ro/blog/rendering-3d-surface-plot-social-media.html" rel="alternate"/><published>2018-08-21T00:00:00+02:00</published><updated>2018-08-21T00:00:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2018-08-21:/blog/rendering-3d-surface-plot-social-media.html</id><summary type="html">&lt;p&gt;It&amp;#8217;s 2018, and your favorite meme pages on facebook constantly come up with quality 3d-post content. How can we mere researchers even begin to compete in terms of social media presence? What chance do we have at going viral? In this post, I show you how to generate 3-d renders of your or your friends&amp;#8217; cool machine learning&amp;nbsp;research.&lt;/p&gt;</summary><content type="html">&lt;p&gt;It&amp;#8217;s 2018, and your favorite meme pages on facebook constantly come up
with quality content like this. (If you&amp;#8217;re on mobile, you will need to
open the below in the Facebook app for the full immersive experience,&amp;nbsp;sadly.)&lt;/p&gt;
&lt;iframe src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Ftheytheytheytheythey%2Fposts%2F1927754023930143&amp;width=350&amp;show_text=true&amp;height=471" width="350" height="471" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true" allow="encrypted-media"&gt;&lt;/iframe&gt;

&lt;p&gt;How can we mere researchers even begin to compete in terms of social media
presence? What chance do we have at going viral? In this post, I show you how
to generate 3-d renders of your or your friends&amp;#8217; cool machine learning&amp;nbsp;research.&lt;/p&gt;
&lt;iframe src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fvlad.niculae%2Fposts%2F2040964392582536&amp;width=350&amp;show_text=true&amp;height=574" width="350" height="574" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true" allow="encrypted-media"&gt;&lt;/iframe&gt;

&lt;p&gt;Without further ado, let&amp;#8217;s make some data-driven surface plots.&lt;label for="sn-getcode" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-getcode" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;&lt;em&gt;&lt;a href="https://github.com/vene/threedposts"&gt;Get code for this&amp;nbsp;post.&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h1 id="threejs"&gt;three.js&lt;a class="headerlink" href="#threejs" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;There seems to be a lot of 3-d modeling software out there, such as
&lt;a href="https://www.blender.org/"&gt;Blender&lt;/a&gt;, but point-and-click interfaces
don&amp;#8217;t seem well-suited for data-driven plots.&lt;label for="sn-blender" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-blender" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;Blender does seem to have a Python &lt;span class="caps"&gt;API&lt;/span&gt;,
but I couldn&amp;#8217;t easily figure out if it supports all we&amp;#8217;re doing here. If you know, leave a&amp;nbsp;comment!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;After a while I settled on &lt;a href="https://threejs.org/"&gt;three.js&lt;/a&gt;,
a JavaScript library for interactive 3-d graphics. I found
&lt;a href="http://stemkoski.github.io/Three.js/Graphulus-Function.html"&gt;Graphulus&lt;/a&gt;: a
three.js example that seems close to what we want. 
Also, three.js supports exporting to &lt;a href="https://en.wikipedia.org/wiki/GlTF"&gt;&lt;span class="caps"&gt;GLTF&lt;/span&gt;&lt;/a&gt;,
the format required to &lt;a href="https://developers.facebook.com/docs/sharing/3d-posts/"&gt;upload 3-d posts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The strategy used by Graphulus uses the three.js
&lt;a href="https://threejs.org/docs/index.html#api/geometries/ParametricGeometry"&gt;ParametricGeometry&lt;/a&gt;:
a 3-d geometry defined by a&amp;nbsp;function &lt;code&gt;z = f(x, y)&lt;/code&gt;. This seems to be exactly
what we want! If we can&amp;nbsp;implement &lt;code&gt;f&lt;/code&gt; easily in &lt;span class="caps"&gt;JS&lt;/span&gt;, we are&amp;nbsp;done! &lt;/p&gt;
&lt;p&gt;In most&amp;nbsp;cases, &lt;code&gt;f&lt;/code&gt; is complicated and took months to implement, using Python or
C++, so we might not be able (or willing) to rewrite it in &lt;span class="caps"&gt;JS&lt;/span&gt;. I see two
possible paths to take&amp;nbsp;here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Building a web app, and&amp;nbsp;implementing &lt;code&gt;f&lt;/code&gt; as an &lt;span class="caps"&gt;API&lt;/span&gt;&amp;nbsp;call.&lt;/li&gt;
&lt;li&gt;Finding a way to draw a three.js surface plot from precomputed&amp;nbsp;values.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Five years ago I would have been eager to take approach 1 and build an
overcomplicated solution, but this time I decided 2 is&amp;nbsp;lazier.&lt;/p&gt;
&lt;p&gt;I eventually found &lt;a href="https://bl.ocks.org/grahampullan/b3beb793b10382c13f7a34c843156a8c"&gt;this
example&lt;/a&gt;
of building a 3-d plot in three.js from a grid of points. In the rest of this
blog, I will walk through the resulting&amp;nbsp;method.&lt;/p&gt;
&lt;h1 id="computing-the-function-values"&gt;Computing the function values&lt;a class="headerlink" href="#computing-the-function-values" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;First thing to do is to compute the value&amp;nbsp;of &lt;code&gt;f&lt;/code&gt; on a grid of points. To keep
things simple, we will&amp;nbsp;plot &lt;span class="arithmatex"&gt;\(\operatorname{softmax}([x, y, 0])_2\)&lt;/span&gt;
as a function&amp;nbsp;of &lt;span class="arithmatex"&gt;\(a\)&lt;/span&gt; and &lt;span class="arithmatex"&gt;\(b\)&lt;/span&gt;. Recall&amp;nbsp;that
&lt;span class="arithmatex"&gt;\(\operatorname{softmax}(\boldsymbol{\theta}) = \boldsymbol{p}\)&lt;/span&gt;,&amp;nbsp;where
&lt;span class="arithmatex"&gt;\(p_i = \frac{\exp(\theta_i)}{\sum_j \exp(\theta_j)}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;We can&amp;nbsp;implement &lt;code&gt;f(x, y)&lt;/code&gt; easily:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# generate.py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;computes p[1] where p = softmax([x, y, 0])&lt;/span&gt;

&lt;span class="sd"&gt;    (it&amp;#39;s p[1], not p[2], because in math we index from 1&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;theta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, let&amp;#8217;s define a grid of points. This is more or less the same
as for making a &lt;a href="https://matplotlib.org/examples/mplot3d/surface3d_demo.html"&gt;3-d surface plot in
matplotlib&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# generate.py (part 2)&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;111&lt;/span&gt; 
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;111&lt;/span&gt; 

&lt;span class="n"&gt;xs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meshgrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty_like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;column_stack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;Z&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The easiest way to copy data between Python and &lt;span class="caps"&gt;JS&lt;/span&gt; is using json.
Indeed, we can very easily generate a valid &lt;span class="caps"&gt;JS&lt;/span&gt;&amp;nbsp;file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# generate.py (part 3)&lt;/span&gt;
&lt;span class="c1"&gt;# output points to a javascript file&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;x&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;y&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;z&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tolist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;var n = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;
&lt;span class="s2"&gt;var m = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;
&lt;span class="s2"&gt;var data = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;data.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Run this script to obtain&amp;nbsp;a &lt;code&gt;data.js&lt;/code&gt; file describing the point cloud that we
want to&amp;nbsp;visualize.&lt;/p&gt;
&lt;h1 id="constructing-the-3-d-surface"&gt;Constructing the 3-d surface&lt;a class="headerlink" href="#constructing-the-3-d-surface" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Once we have the 3-d points, it&amp;#8217;s time to use three.js to construct
the 3-d object. First, download &lt;a href="https://github.com/mrdoob/three.js/raw/r95/build/three.js"&gt;three.js&lt;/a&gt; itself, as well the
&lt;a href="https://raw.githubusercontent.com/mrdoob/three.js/r95/examples/js/exporters/GLTFExporter.js"&gt;GLTFExporter.js&lt;/a&gt; file.&lt;label for="sn-threejsversion" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-threejsversion" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;The links provided are for version r95, the version I
used. Feel free to try newer versions; new features may&amp;nbsp;appear!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s make a minimal &lt;span class="caps"&gt;HTML&lt;/span&gt; file to load the required&amp;nbsp;libraries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- plot-glb.html --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;content-type&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/html; charset=UTF-8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;three.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GLTFExporter.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;data.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="cm"&gt;&amp;lt;!-- generated above --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;download&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Download .glb&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;plot-glb.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="cm"&gt;&amp;lt;!-- we&amp;#39;ll write this file below --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now it&amp;#8217;s time to do the heavy lifting&amp;nbsp;in &lt;code&gt;plot-glb.js&lt;/code&gt;.
First thing we must do is construct a three.js scene. This consists of a
&lt;em&gt;Mesh&lt;/em&gt;, which in turn is described by a &lt;em&gt;Material&lt;/em&gt; and a 
&lt;em&gt;Geometry&lt;/em&gt; (a set of vertices and faces). The &lt;em&gt;Material&lt;/em&gt; is easy to pick; the
tricky part is the &lt;em&gt;Geometry&lt;/em&gt;, which we will construct&amp;nbsp;manually.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;makeScene&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Geometry&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tomato&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// make a long sequence containing our 3-d points&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nverts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nverts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newvert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vertices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newvert&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// build triangular faces (top and bottom) between adjacent points&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;face1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Face3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;face2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Face3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;face1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;face2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Compute normals for shading&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;computeFaceNormals&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;computeVertexNormals&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Give it a pretty material&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MeshLambertMaterial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;side&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DoubleSide&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0xffffff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;vertexColors&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FaceColors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;emissive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x111111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Scene&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mesh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Normally, the next step would be to add
&lt;a href="https://threejs.org/docs/index.html#api/cameras/PerspectiveCamera"&gt;cameras&lt;/a&gt;
and &lt;a href="https://threejs.org/docs/index.html#api/lights/SpotLight"&gt;lights&lt;/a&gt;, maybe
some &lt;a href="https://threejs.org/docs/index.html#api/helpers/ArrowHelper"&gt;axes&lt;/a&gt; and
descriptive
&lt;a href="https://threejs.org/docs/index.html#manual/introduction/Creating-text"&gt;text&lt;/a&gt;,
and render to&amp;nbsp;a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;. But our goal is to make a Facebook 3-d post, so we can
simply export the current scene as a binary &lt;span class="caps"&gt;GLB&lt;/span&gt;&amp;nbsp;file. &lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;GLTF&lt;/span&gt; exporter will give us a byte buffer that we need to download. We can
handle with a function that modifies our&amp;nbsp;link: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// plot-glb.js (part 2)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;saveArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application/octet-stream&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;download&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, we use&amp;nbsp;the &lt;code&gt;GLTFExporter&lt;/code&gt; to generate a Facebook-compatible 3-d&amp;nbsp;model.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// plot-glb.js (part 3)&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;makeScene&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exporter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GLTFExporter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;trs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;onlyVisible&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;truncateDrawRange&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;forceIndices&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// for Facebook&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;forcePowerOfTwoTextures&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// for Facebook&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;glb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;saveArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;glb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;scene.glb&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, if you&amp;nbsp;open &lt;code&gt;plot-glb.html&lt;/code&gt; and click the Download link, you should get&amp;nbsp;a
&lt;code&gt;.glb&lt;/code&gt; file that can be drag-and-dropped onto a new Facebook&amp;nbsp;post.&lt;/p&gt;
&lt;h1 id="but-it-shows-up-seen-from-above-and-looks-ugly"&gt;But it shows up seen from above and looks ugly!&lt;a class="headerlink" href="#but-it-shows-up-seen-from-above-and-looks-ugly" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Indeed, there seems to be no way to specify the camera angle in a Facebook post.
&lt;a href="https://threejs.org/docs/index.html#api/cameras/PerspectiveCamera"&gt;Three.js cameras&lt;/a&gt;
are not part of the &lt;em&gt;scene&lt;/em&gt;,&lt;label for="sn-scene" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-scene" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;Unlike the folks below, who are definitely part of &lt;em&gt;the scene&lt;/em&gt;:
&lt;a title="By Kirsten Hartsoch [2] ([1]) [CC BY 2.0 (https://creativecommons.org/licenses/by/2.0)], via Wikimedia Commons"
 href="https://commons.wikimedia.org/wiki/File:Scene_kids2.jpg"&gt;&lt;img width="256"
 alt="Scene kids2"
 src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Scene_kids2.jpg/256px-Scene_kids2.jpg"&gt;&lt;/a&gt;
&lt;/span&gt;
so I think they don&amp;#8217;t get exported in a &lt;span class="caps"&gt;GLTF&lt;/span&gt;/&lt;span class="caps"&gt;GLB&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;But now that we know that the default camera is somewhere along the Z axis above
the plot, we can simply rotate our point cloud to get to a more aesthetically
pleasing&amp;nbsp;angle! &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# generate.py (part 2.5)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotate3d_x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotate3d_z&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rotate3d_z&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rotate3d_x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Above, we used &lt;em&gt;rotation matrices&lt;/em&gt; to rotate the point cloud around.&lt;label for="sn-rotation" class="margin-toggle sidenote-number"&gt;&lt;/label&gt;&lt;input type="checkbox" id="sn-rotation" class="margin-toggle"/&gt;&lt;span class="sidenote"&gt;Rotating a 3-d point is equivalent to multiplying by a
certain orthogonal matrix. &lt;a href="https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations"&gt;The wikipedia article&lt;/a&gt; lists formulas for
rotation matrices against the canonical&amp;nbsp;axes &lt;span class="arithmatex"&gt;\(x\)&lt;/span&gt;, &lt;span class="arithmatex"&gt;\(y\)&lt;/span&gt;, &lt;span class="arithmatex"&gt;\(z\)&lt;/span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The result should look just like the 3-d post I made below.
Of course,&amp;nbsp;in &lt;code&gt;generate.py&lt;/code&gt; we can do arbitrary complicated calculations&amp;nbsp;in &lt;code&gt;f&lt;/code&gt;,
including numerical optimization, so this approach is quite powerful. May it
bring you many&amp;nbsp;likes!&lt;/p&gt;
&lt;p&gt;&lt;iframe
src="https://www.facebook.com/plugins/post.php?href=https%3A%2F%2Fwww.facebook.com%2Fvlad.niculae%2Fposts%2F2040960132582962&amp;width=350&amp;show_text=true&amp;height=535"
width="350" height="535" style="border:none;overflow:hidden" scrolling="no"
frameborder="0" allowTransparency="true" allow="encrypted-media"&gt;&lt;/iframe&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/vene/threedposts"&gt;Get code for this&amp;nbsp;post.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="presentation"/></entry><entry><title>Winning Arguments and Attitude Change on Reddit</title><link href="//vene.ro/blog/winning-arguments-attitude-change-reddit-cmv.html" rel="alternate"/><published>2016-03-04T12:00:00+01:00</published><updated>2016-03-04T12:00:00+01:00</updated><author><name>vene &amp; Chenhao Tan</name></author><id>tag:vene.ro,2016-03-04:/blog/winning-arguments-attitude-change-reddit-cmv.html</id><summary type="html">&lt;p&gt;One of the many reasons why people are beautiful and fascinating is the way we change.  I get reminded of how much I have changed everytime I see a photo of myself from high school, and this always triggers memories of how my opinions and attitudes have changed over the years. Some of the beliefs I held most strongly ended up being abandoned completely, while others have not changed much.&amp;nbsp;&amp;#8230;&lt;/p&gt;</summary><content type="html">&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;em&gt;I have the honor of featuring another guest post on my blog. This time, we have with us my friend &lt;a href="https://chenhaot.com"&gt;Chenhao Tan&lt;/a&gt;. We recently collaborated on &lt;a href="https://chenhaot.com/pages/changemyview.html"&gt;an effort to understand how people change their minds&lt;/a&gt; on the &lt;a href="http://reddit.com/r/changemyview"&gt;&lt;code&gt;/r/ChangeMyView&lt;/code&gt;&lt;/a&gt;&amp;nbsp;subreddit.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://nbviewer.jupyter.org/github/vene/vene.github.io/blob/pelican/content/blog/winning_arguments.ipynb"&gt;Source of this Jupyter&amp;nbsp;Notebook.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;One of the many reasons why people are beautiful and fascinating is the way we change.  I get reminded of how much I have changed everytime I see a photo of myself from high school, and this always triggers memories of how my opinions and attitudes have changed over the years. Some of the beliefs I held most strongly ended up being abandonded completely, while others have not changed much. &lt;span style="color: red"&gt;&lt;em&gt;If you&amp;#8217;re bored already, scroll down, this blog post has code&amp;nbsp;too!&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Attitude change and persuasion has been an appealing research topic for a long time.  One of the more interesting theories on this issue is &lt;a href="http://youarenotsosmart.com/2011/06/10/the-backfire-effect/"&gt;&lt;em&gt;the backfire effect&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When your deepest convictions are challenged by contradictory evidence, your beliefs get&amp;nbsp;stronger.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Earlier studies, however, suggest a lot more nuance. For instance, individuals who &lt;a href="https://en.wikipedia.org/wiki/Self-affirmation"&gt;&lt;em&gt;affirm their self&lt;/em&gt;&lt;/a&gt; were &lt;a href="https://ed.stanford.edu/sites/default/files/when_beliefs_yield.pdf"&gt;found&lt;/a&gt; to be more likely to be persuaded by evidence.  Studying opinion change &lt;em&gt;in the wild&lt;/em&gt; can be difficult.  Many times, people change their mind slowly and subtly, over a number of years, making it difficult to pinpoint the exact moment or cause of the change.  Also, a change of opinion on a minor, inconsequential topic may not be worth&amp;nbsp;discussing.&lt;/p&gt;
&lt;p&gt;We contributed to these efforts by studying &lt;a href="http://reddit.com/r/changemyview"&gt;&lt;code&gt;/r/ChangeMyView&lt;/code&gt;&lt;/a&gt; (&lt;span class="caps"&gt;CMV&lt;/span&gt;), an online forum of civil, open-minded discussion where people invite other members to challenge their more controversial views.  If you&amp;#8217;ve never visited &lt;span class="caps"&gt;CMV&lt;/span&gt;, the concept that people can debate hot topics without getting into flame wars on the internet might be hard to believe.  Or, if you read a lot of Youtube comments, you might be inclined to believe &lt;em&gt;the backfire effect&lt;/em&gt;, and doubt that graceful concession is even possible online.  But a quick trip to this great subreddit will undoubtably make you a&amp;nbsp;believer.&lt;/p&gt;
&lt;h2 id="What-makes-/r/ChangeMyView-special?"&gt;What&amp;nbsp;makes &lt;code&gt;/r/ChangeMyView&lt;/code&gt; special?&lt;a class="anchor-link" href="#What-makes-/r/ChangeMyView-special?"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Some of the more interesting reasons why &lt;span class="caps"&gt;CMV&lt;/span&gt; is a cool lens for the study of persuasion&amp;nbsp;is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Original posters (&lt;span class="caps"&gt;OP&lt;/span&gt;) select views that are important to them and controversial, but, by posting them there, manifest a &lt;em&gt;de facto&lt;/em&gt; openness to&amp;nbsp;counterarguments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For an &lt;span class="caps"&gt;OP&lt;/span&gt;&amp;#8217;s opinion, there are usually multiple, high-quality persuasion attempts, but most are unsuccessful. By comparing challenges to the same person&amp;#8217;s opinion, we avoid the &lt;em&gt;trap&lt;/em&gt; of actually finding that some issues are easier to challenge than&amp;nbsp;others.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Successful arguments are explicitly marked with a delta (Δ). &lt;em&gt;Hooray for supervised&amp;nbsp;learning!&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Even in light of &lt;span class="caps"&gt;CMV&lt;/span&gt; users being open-minded, &lt;em&gt;not all opinions are changed&lt;/em&gt;, and not just due to a lack of trying. This gives us a chance to analyze &lt;em&gt;resistance to persuasion&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Taking-a-look-at-the-dataset"&gt;Taking a look at the dataset&lt;a class="anchor-link" href="#Taking-a-look-at-the-dataset"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I know what you&amp;#8217;re thinking, a little less conversation, let&amp;#8217;s actually look at some examples.  Sure, our pleasure! Let&amp;#8217;s download the dataset that we&amp;nbsp;compiled.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; If you&amp;#8217;re following along at home, and you don&amp;#8217;t already have it, this will download the 321M&amp;nbsp;file &lt;code&gt;cmv.tar.bz2&lt;/code&gt;.  We kept the code simple, but the downside is that your Jupyter kernel will be busy until the file is downloaded.  If, by any chance, your connection drops or you interrupt it, you will have to remove the partial file, otherwise the rest of the notebook might not&amp;nbsp;work.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [1]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# load the data&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tarfile&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os.path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bz2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BZ2File&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;


&lt;span class="n"&gt;fname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cmv.tar.bz2"&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://chenhaot.com/data/cmv/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;

&lt;span class="c1"&gt;# download if not exists&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f_disk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;f_disk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# save to disk too&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;tar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileobj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Extract the file we are interested in&lt;/span&gt;

&lt;span class="n"&gt;train_fname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"op_task/train_op_data.jsonlist.bz2"&lt;/span&gt;
&lt;span class="n"&gt;test_fname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"op_task/heldout_op_data.jsonlist.bz2"&lt;/span&gt;

&lt;span class="n"&gt;train_bzlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extractfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Deserialize the JSON list&lt;/span&gt;
&lt;span class="n"&gt;original_posts_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BZ2File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_bzlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;test_bzlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extractfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;original_posts_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'utf-8'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BZ2File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_bzlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;The archive we just downloaded contains all the data we used in our experiments.  We invite you to explore the other files in there: you can use&amp;nbsp;the &lt;code&gt;pair_task&lt;/code&gt; folder to study pairs of similar argumentative threads made in reply to the same original post, one successful and one not (more details in Section 4 of the paper).  We also provide you with the full, non-processed data in&amp;nbsp;the &lt;code&gt;all&lt;/code&gt; subfolder, so that you can design new interesting&amp;nbsp;tasks.&lt;/p&gt;
&lt;p&gt;In this blog post, we are going to focus on the original posts, rather than the arguments made in response to them.  In the final part of our paper, we explored &lt;em&gt;resistance to persuasion&lt;/em&gt;: recognizing whether an &lt;span class="caps"&gt;OP&lt;/span&gt; is likely to be persuaded or not, given how they express their view.  We insist on this because we think it&amp;#8217;s interesting and we know there&amp;#8217;s plenty more work to be done on this task: we merely scratched the surface! So, hopefully, this quick tour will make you want to work on&amp;nbsp;this.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [2]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;IPython.display&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Markdown&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmv_post&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;md_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"**&lt;/span&gt;&lt;span class="si"&gt;{title}&lt;/span&gt;&lt;span class="s2"&gt;** &lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{selftext}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;cmv_post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;md_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt; "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;md_format&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md_format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [3]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;show_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original_posts_train&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[3]:&lt;/div&gt;
&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/markdown"&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;CMV&lt;/span&gt;: Taxation should be voluntary, with individuals retaining the right to opt out of&amp;nbsp;it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let me preface this argument by saying that I don&amp;#8217;t particularly agree with the Tea Party. I&amp;#8217;m a Democrat who votes for candidates that support ending the poverty trap, expanding social services, and wish to improve fundamental institutions like education or military service. With that said, I also believe in the concept of personal liberty. This includes the right to refuse taxation upon the basis of&amp;#8230; well, whatever the fuck you&amp;nbsp;want.&lt;/p&gt;
&lt;p&gt;I know what your thinking, &amp;#8220;But how could we fund things like food stamps if shitty people are like &amp;#8216;fuck those lazy blacks that suck up muh moneyz&amp;#8217;?&amp;#8221; However, in order to participate in the American system of democracy, one must agree to pay taxes unconditionally. A citizen ought not be able to decide which services he or she pays for. Instead, one can only receive protection from, say, the police if they pay every tax mandated by the local, state, or federal government.
In my opinion, this would create a voluntary political system where individual rights are maintained. If we believe that the government overwhelmingly improves our own conditions, as I do, then only a small amount of citizens would opt out of public services because they disagree with certain elements of policy. Within the framework of government existing because it benefits the people, no one should expect a sudden drop in tax revenues because citizens are intelligent enough to understand how their own interests are promoted by the state on&amp;nbsp;balance.&lt;/p&gt;
&lt;p&gt;So go forth and &lt;span class="caps"&gt;CMV&lt;/span&gt;, noble&amp;nbsp;redditors!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;&amp;gt; &lt;em&gt;Hello, users of &lt;span class="caps"&gt;CMV&lt;/span&gt;! This is a footnote from your moderators. We&amp;#8217;d just like to remind you of a couple of things. Firstly, please remember to&lt;/em&gt; &lt;em&gt;&lt;strong&gt;&lt;a href="http://www.reddit.com/r/changemyview/wiki/rules"&gt;read through our rules&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;. &lt;em&gt;If you see a comment that has broken one, it is more effective to report it than downvote it. Speaking of which,&lt;/em&gt; &lt;em&gt;&lt;strong&gt;&lt;a href="http://www.reddit.com/r/changemyview/wiki/guidelines#wiki_upvoting.2Fdownvoting"&gt;downvotes don&amp;#8217;t change views&lt;/a&gt;****! If you are thinking about submitting a &lt;span class="caps"&gt;CMV&lt;/span&gt; yourself, please have a look through our* ***&lt;a href="http://www.reddit.com/r/changemyview/wiki/populartopics"&gt;popular topics wiki&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt; &lt;em&gt;first. Any questions or concerns? Feel free to&lt;/em&gt; &lt;em&gt;&lt;strong&gt;&lt;a href="http://www.reddit.com/message/compose?to=/r/changemyview"&gt;message us&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;. &lt;em&gt;Happy&amp;nbsp;CMVing!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;What do you think, has this &lt;span class="caps"&gt;OP&lt;/span&gt;&amp;#8217;s view been&amp;nbsp;changed?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;#8230; drumroll&amp;nbsp;&amp;#8230;&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [4]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;original_posts_train&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"delta_label"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[4]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain"&gt;
&lt;pre&gt;False&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;This poster has not been successfully persuaded! And just in case you have any doubts that no attempts were made or something, (we checked, trust us), we could go take a look at all comments and discussion by following&amp;nbsp;the &lt;code&gt;post_id&lt;/code&gt; to the original post on&amp;nbsp;Reddit:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [5]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;original_posts_train&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"_"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"http://reddit.com/r/changemyview/comments/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;http://reddit.com/r/changemyview/comments/2q03zk
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Just by reading the post, it&amp;#8217;s obvious that some cleanup needs to be done: we should strip the moderator-added footnotes, edits and quotes. We will do this by removing lines starting with &amp;#8220;&amp;gt;&amp;#8221;, lines starting with &amp;#8220;____&amp;#8221;, and lines where the string &amp;#8220;edit&amp;#8221; occurs in the first 2 &amp;#8220;words&amp;#8221;. &lt;em&gt;(In the code used for our research, we used &lt;strong&gt;a more careful heuristic&lt;/strong&gt; that involves actual tokenization, but we will keep it simple here and split at white&amp;nbsp;spaces.)&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [6]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmv_post&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cmv_post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
             &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"____"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s2"&gt;"edit"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [7]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;show_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;original_posts_train&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
               &lt;span class="n"&gt;selftext&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original_posts_train&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'selftext'&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[7]:&lt;/div&gt;
&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/markdown"&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;CMV&lt;/span&gt;: Taxation should be voluntary, with individuals retaining the right to opt out of&amp;nbsp;it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let me preface this argument by saying that I don&amp;#8217;t particularly agree with the Tea Party. I&amp;#8217;m a Democrat who votes for candidates that support ending the poverty trap, expanding social services, and wish to improve fundamental institutions like education or military service. With that said, I also believe in the concept of personal liberty. This includes the right to refuse taxation upon the basis of&amp;#8230; well, whatever the fuck you&amp;nbsp;want.&lt;/p&gt;
&lt;p&gt;I know what your thinking, &amp;#8220;But how could we fund things like food stamps if shitty people are like &amp;#8216;fuck those lazy blacks that suck up muh moneyz&amp;#8217;?&amp;#8221; However, in order to participate in the American system of democracy, one must agree to pay taxes unconditionally. A citizen ought not be able to decide which services he or she pays for. Instead, one can only receive protection from, say, the police if they pay every tax mandated by the local, state, or federal government.
In my opinion, this would create a voluntary political system where individual rights are maintained. If we believe that the government overwhelmingly improves our own conditions, as I do, then only a small amount of citizens would opt out of public services because they disagree with certain elements of policy. Within the framework of government existing because it benefits the people, no one should expect a sudden drop in tax revenues because citizens are intelligent enough to understand how their own interests are promoted by the state on&amp;nbsp;balance.&lt;/p&gt;
&lt;p&gt;So go forth and &lt;span class="caps"&gt;CMV&lt;/span&gt;, noble&amp;nbsp;redditors!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Now that the cleanup is done, we can think a bit about what, and how, the &lt;span class="caps"&gt;OP&lt;/span&gt; is trying to express here.  We can see several strategies at&amp;nbsp;play:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prefacing with a cop-out attempting to make their opinion seem more&amp;nbsp;reasonable.&lt;/li&gt;
&lt;li&gt;Liberal (pun intended) use of casual language, slang, curse&amp;nbsp;words.&lt;/li&gt;
&lt;li&gt;Lots of self-asserting: &amp;#8220;I know, &amp;#8220;I believe&amp;#8221;, &amp;#8220;I&amp;#8217;m a Democrat&amp;#8221;,&amp;nbsp;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is plenty of room for speculation, exploration, finding patterns, and connecting this to existing literature.  Let&amp;#8217;s focus on one of our&amp;nbsp;results:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Reproducing-some-results"&gt;Reproducing some results&lt;a class="anchor-link" href="#Reproducing-some-results"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;One of our findings was that OPs who express their view using &amp;#8220;we&amp;#8221; are more stubborn than the ones who say &amp;#8220;I&amp;#8221;. The example above uses some plural pronouns (&amp;#8220;If we believe&amp;#8221;) but overall it seems the opinion is purely self-attributed.  Does this pattern hold in the&amp;nbsp;dataset?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [8]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pron_i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ive'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"i'd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"i'm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'myself'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'mine'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'me'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'im'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"i'll"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'i'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"i've"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'my'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [9]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pron_we&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"we'd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"we're"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'lets'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'weve'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'we'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'our'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'ourselves'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"let's"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'ours'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'us'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"we'll"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s2"&gt;"we've"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [10]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pron_we_re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b(&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"|"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pron_we&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pron_i_re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b(&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"|"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pron_i&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [11]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pron_we_re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"We let ourselves go"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[11]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain"&gt;
&lt;pre&gt;['We', 'ourselves']&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [12]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;n_we&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;n_i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;original_posts_train&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"selftext"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;denom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;n_we&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pron_we_re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;n_i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pron_i_re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;denom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# there is one instance that consists only of edits.&lt;/span&gt;
        &lt;span class="c1"&gt;# It's possible that the OP deleted the rest of the post.&lt;/span&gt;
        &lt;span class="n"&gt;n_we&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;n_i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [13]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;n_we&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_we&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;n_i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;is_malleable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"delta_label"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;original_posts_train&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [14]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="k"&gt;matplotlib&lt;/span&gt; inline
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;seaborn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;sns&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="application/vnd.jupyter.stderr"&gt;
&lt;pre&gt;/home/vlad/conda/lib/python3.4/site-packages/IPython/html.py:14: ShimWarning: The `IPython.html` package has been deprecated. You should import from `notebook` instead. `IPython.html.widgets` has moved to `ipywidgets`.
  "`IPython.html.widgets` has moved to `ipywidgets`.", ShimWarning)
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [15]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color_palette&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [16]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;scipy.stats&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [17]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compare_bars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;neg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;neg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
        &lt;span class="n"&gt;yerr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.96&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;1.96&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neg&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;  &lt;span class="c1"&gt;# ~95% ci&lt;/span&gt;
        &lt;span class="n"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'center'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;error_kw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ecolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capthick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xticks&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Malleable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Stubborn"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;fontsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yticks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fontsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"First person plural&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;('we')"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fontsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;compare_bars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_we&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_malleable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Proportion of words"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fontsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"First person singular&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;('I')"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fontsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;compare_bars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_malleable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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
OH78OHv27MHKyqrc9YvT5iZ09+5dcnJySv1XXFxc5vJmZmZ07txZq0ypVCLLMj/99JNWeePGjYmN
jdU6zVdRtWvX5rXXXtMqe+2115BlWXNqMSUlhaysLLp27cqdO3e02tOpUyfMzMxITk7WWocsyzrr
LWnHw6fqVCoVa9eupWfPnsD9U2m3bt0iKChI8wVSonfv3gDs27dPpy0PfonB34OUrl27ZsiuoEmT
JpqOu8TD+8JYAgICtDpugKFDh7Ju3TpNx52bm0tOTg4NGjTQdPiCfiLv7qtOefe4+1eSJAYOHKjp
uAFeeOEFVCqV1mWmo0ePolAotA6UAK0DHGPp0aMHCQkJmo47Ly+PnJwczdkYfTns4+NjUMcN4pe3
SS1fvpxly5bpfU+SJM11jtKoVCqt01IA/fr149tvv+WTTz4hKSmJDh064OPjQ+vWrXU6gIpydnbW
OfJ8/vnnATTXdtLS0gBYtGgRixYt0tuu//73vzrlD9/aERQUxPr164mPj2ffvn106tSJ9u3b065d
O61TfxcuXECSJL2389SpUwc7Ozuda4MKhYIGDRpoldWsWROAe/fu6W37w/Rt7+F9YQySJOnEClBY
WMiqVavYvn07mZmZWnFLkkRhYaHRYnjaiLz7W3XJO2PsX0dHR50yc3NzrVz5448/cHBw0Pn7Ojo6
UqdOHW7evGnQtgyVmJjIpk2buHjxInfv3tWUl5bD+r4LSiM6bxN66623yjy12ahRozKXr1Wrlk5Z
w4YN+eabb1i7di27d+9m5cqVrFixApVKRWRkJH379n3kePUd8ZUcdRcUFAD3jx4lSWL48OH4+/vr
XU9JwpYwNzfXOiKG+18AGzduJD4+nu3btxMXF8cXX3yBtbU1Q4cOJSIiAoDbt2+XGhvcv3Xk4cE6
CoXisU9/GbIvjEXf3/n9999n27ZteHp6MnPmTOrXr49SqeTw4cP885//NOr2nzYi7+6rTnlnjP37
cPv1uXPnjtZ15gfZ2NgYtfNesmQJK1aswNXVlalTp9KwYUPMzc05f/48M2fO1LuMvs9eaUTnbUKO
jo60adPG6OutW7cu77//Pu+//z5nz55l7969rFu3jpkzZ1KrVq1HvhZ6584dnbKcnBzgftLD/Q+X
LMvY2to+dttq165NZGQkkZGRZGZmsnfvXtavX8/SpUtRKBSEh4drvjxKvkwelp+fX6EPvKEM2ReP
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
rkJggg==
"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Training-a-simple-classifier"&gt;Training a simple classifier&lt;a class="anchor-link" href="#Training-a-simple-classifier"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;We can take this one step further and train a simple bag-of-words classifier using &lt;a href="http://scikit-learn.org/"&gt;&lt;code&gt;scikit-learn&lt;/code&gt;&lt;/a&gt;.  Bag-of-words is often a good baseline, and a good &lt;em&gt;debugging&lt;/em&gt; tool: sometimes, problems are simpler than we think, and bag-of-words can catch this and hint that we&amp;#8217;re being too optimistic.  (For an example of what I mean, try running the code below without the &amp;#8220;cleanup&amp;#8221;&amp;nbsp;step.)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [18]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.feature_extraction.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TfidfVectorizer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.cross_validation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;train_test_split&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.linear_model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LogisticRegressionCV&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.metrics.scorer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;roc_auc_scorer&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [19]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# prepare the data in Python list form&lt;/span&gt;

&lt;span class="n"&gt;op_text_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original_post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'selftext'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;# original_post['selftext']  # try this at home&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;original_post&lt;/span&gt;
    &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;original_posts_train&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;op_text_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original_post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'selftext'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;# original_post['selftext']  # try this at home&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;original_post&lt;/span&gt;
    &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;original_posts_test&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;We will use a simple term-frequency vectorizer with no &lt;span class="caps"&gt;IDF&lt;/span&gt; and no data-dependent thresholds. This allows us to vectorize all of the training data once, and then do efficient model selection using LogisticRegressionCV, without having to vectorize again for each cross-validation&amp;nbsp;fold.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Side note: *This is important!  Imagine we wanted to throw away rare words, to avoid overfitting to them. We could use, for&amp;nbsp;example, &lt;code&gt;min_df=10&lt;/code&gt;.  This would keep all words that appear 10 times in the *training data&lt;/em&gt;.  But, in a moment, we will be doing 3-fold cross-validation.  Consider the first cross-validation split: what if we encounter a word that occurs 5 times in the train fold and 5 times in the validation fold?  If we vectorized &lt;em&gt;outside&lt;/em&gt; of the &lt;span class="caps"&gt;CV&lt;/span&gt; loop, we would keep this word, and assign it a weight estimated from very few observations.  This could lead us to overestimate our validation&amp;nbsp;performance.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [20]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;vect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TfidfVectorizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;use_idf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'l1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;X_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op_text_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [21]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LogisticRegressionCV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_weight&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'balanced'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;scoring&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'roc_auc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'sag'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [22]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_malleable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[22]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain"&gt;
&lt;pre&gt;LogisticRegressionCV(Cs=10, class_weight='balanced', cv=None, dual=False,
           fit_intercept=True, intercept_scaling=1.0, max_iter=500,
           multi_class='ovr', n_jobs=1, penalty='l2', random_state=0,
           refit=True, scoring='roc_auc', solver='sag', tol=0.001,
           verbose=0)&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [23]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Evaluating on the heldout data:&lt;/span&gt;

&lt;span class="n"&gt;X_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op_text_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;is_malleable_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"delta_label"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;original_posts_test&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;test_roc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roc_auc_scorer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_malleable_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Test ROC AUC score: &lt;/span&gt;&lt;span class="si"&gt;{:.3f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_roc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;Test ROC AUC score: 0.531
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;The score is in the same ballpark as the results in our paper, with the differences coming from the more naive tokenization and preprocessing used here.  The &lt;span class="caps"&gt;ROC&lt;/span&gt; &lt;span class="caps"&gt;AUC&lt;/span&gt; score (area under the &lt;span class="caps"&gt;ROC&lt;/span&gt; curve) can be interpreted as the probability that, given a pair of a malleable and a stubborn &lt;span class="caps"&gt;OP&lt;/span&gt;, the classifier will successfully tell which is which.  This metric is particularly good for imbalanced datasets, and 50% corresponds to an uninformed classifier (one that responds randomly or always gives the same answer).  In light of this, the score is not great, confirming that this is not at all an easy problem, still open for further&amp;nbsp;research.&lt;/p&gt;
&lt;p&gt;We point out one particular challenge: our heldout data is temporally newer than the training data, because we collected it &lt;em&gt;strictly after&lt;/em&gt; developing our work on the training set.  This has the advantage of making overfitting impossible, but &lt;em&gt;extrapolating&lt;/em&gt; is different than &lt;em&gt;interpolating&lt;/em&gt; in time, and some attributes of the &lt;span class="caps"&gt;CMV&lt;/span&gt; community shift over time.  (We discuss some of them in Section 2 of our paper.) You may model malleability better by accounting for shifts both in covariates and the outcome&amp;nbsp;variable.&lt;/p&gt;
&lt;h2 id="Checking-out-the-features"&gt;Checking out the features&lt;a class="anchor-link" href="#Checking-out-the-features"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;We can use the trained model to see what words have high and low weights. You could use the output for further inspiration.  For instance, this could suggest that generalizing words (&amp;#8220;anything&amp;#8221;, &amp;#8220;anyone&amp;#8221;, &amp;#8220;nothing&amp;#8221;) might be a sign of stubbornness.  There is plenty of potential for exploring and expanding our work further, so give it a&amp;nbsp;try!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [24]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;top_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argsort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;coef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i_hi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i_lo&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;row_vals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coef&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i_hi&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i_hi&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;coef&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i_lo&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i_lo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{:&amp;gt;4.1f}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{:&amp;lt;20}&lt;/span&gt;&lt;span class="s2"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{:&amp;gt;4.1f}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{:&amp;lt;20}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;row_vals&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [25]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;top_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coef_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;vect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_feature_names&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;[ 8.9] so                  [-9.1] its                 
[ 7.2] even                [-5.5] having              
[ 5.6] feel                [-5.3] anyone              
[ 5.5] would               [-5.2] government          
[ 5.5] being               [-5.2] right               
[ 5.4] more                [-5.2] anything            
[ 5.3] please              [-5.2] our                 
[ 5.3] hard                [-5.0] certain             
[ 5.3] doesn               [-5.0] world               
[ 5.2] ll                  [-5.0] nothing             
[ 5.1] when                [-5.0] minimum             
[ 5.0] system              [-4.7] we                  
[ 4.7] philosophy          [-4.7] men                 
[ 4.5] subject             [-4.6] life                
[ 4.4] help                [-4.5] population          
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Much of the social interaction we do online these days can prove useful for research.  In our work, we investigated persuasion and attitude change&amp;nbsp;on &lt;code&gt;/r/ChangeMyView&lt;/code&gt;.  In this post, we walked through part of the data that we collected and made available.  Hoping that this sparks some inspiration, we are looking forward to hearing what cool new things you will&amp;nbsp;discover!&lt;/p&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [26]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="k"&gt;load_ext&lt;/span&gt; watermark
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [27]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="k"&gt;watermark&lt;/span&gt; -d -v --packages numpy,scipy,scikit-learn,seaborn -m
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;2016-03-04 

CPython 3.4.4
IPython 4.1.1

numpy 1.10.4
scipy 0.17.0
scikit-learn 0.17.1
seaborn 0.7.0

compiler   : GCC 4.4.7 20120313 (Red Hat 4.4.7-1)
system     : Linux
release    : 4.4.0-040400rc4-generic
machine    : x86_64
processor  : x86_64
CPU cores  : 4
interpreter: 64bit
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = '//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
    mathjaxscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'AMS' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: 'center'," +
        "    displayIndent: '0em'," +
        "    showMathMenu: true," +
        "    tex2jax: { " +
        "        inlineMath: [ ['$','$'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        " linebreaks: { automatic: true, width: '95% container' }, " +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'black ! important'} }" +
        "    } " +
        "}); ";
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;
</content><category term="social-nlp"/><category term="persuasion"/><category term="malleability"/><category term="attitude change"/><category term="reddit"/><category term="social"/></entry><entry><title>Word Mover’s Distance in Python</title><link href="//vene.ro/blog/word-movers-distance-in-python.html" rel="alternate"/><published>2015-11-07T12:00:00+01:00</published><updated>2015-11-07T12:00:00+01:00</updated><author><name>vene &amp; Matt Kusner</name></author><id>tag:vene.ro,2015-11-07:/blog/word-movers-distance-in-python.html</id><summary type="html">&lt;p&gt;In document classification and other natural language processing applications, having a good measure of the similarity of two texts can be a valuable building block.   Ideally, such a measure would capture semantic information.  Cosine similarity on bag-of-words vectors is known to do well in practice, but it inherently cannot capture when documents say the same thing in completely different words.&amp;nbsp;&amp;#8230;&lt;/p&gt;</summary><content type="html">&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;*A guide to scikit-learn compatible nearest neighbors classification using the recently introduced word mover&amp;#8217;s distance (&lt;span class="caps"&gt;WMD&lt;/span&gt;). *
Joint post with the awesome &lt;a href="http://matthewkusner.com"&gt;Matt Kusner&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://nbviewer.jupyter.org/github/vene/vene.github.io/blob/pelican/content/blog/word-movers-distance-in-python.ipynb"&gt;Source of this Jupyter&amp;nbsp;notebook.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In document classification and other natural language processing applications, having a good measure of the similarity of two texts can be a valuable building block.   Ideally, such a measure would capture semantic information.  Cosine similarity on bag-of-words vectors is known to do well in practice, but it inherently cannot capture when documents say the same thing in completely different&amp;nbsp;words.&lt;/p&gt;
&lt;p&gt;Take, for example, two&amp;nbsp;headlines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Obama speaks to the media in&amp;nbsp;Illinois&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;The President greets the press in&amp;nbsp;Chicago&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These have no content words in common, so according to most bag of words&amp;#8212;based metrics, their distance would be maximal.  (For such applications, you probably don&amp;#8217;t want to count stopwords such as &lt;em&gt;the&lt;/em&gt; and &lt;em&gt;in&lt;/em&gt;, which don&amp;#8217;t truly signal semantic&amp;nbsp;similarity.)&lt;/p&gt;
&lt;p&gt;One way out of this conundrum is the word mover&amp;#8217;s distance (&lt;span class="caps"&gt;WMD&lt;/span&gt;), introduced in
&lt;a href="http://mkusner.github.io/publications/WMD.pdf"&gt;&lt;em&gt;From Word Embeddings To Document Distances&lt;/em&gt;&lt;/a&gt;,
(Matt J. Kusner, Yu Sun, Nicholas I. Kolkin, Kilian Q. Weinberger, &lt;span class="caps"&gt;ICML&lt;/span&gt; 2015).
&lt;span class="caps"&gt;WMD&lt;/span&gt; adapts the &lt;a href="https://en.wikipedia.org/wiki/Earth_mover%27s_distance"&gt;earth mover&amp;#8217;s distance&lt;/a&gt; to the space of documents: the distance between two texts is given by the total amount of &amp;#8220;mass&amp;#8221; needed to move the words from one side into the other, multiplied by the distance the words need to move. So, starting from a measure of the distance between different words, we can get a principled document-level distance. Here is a visualisation of the idea, from the &lt;span class="caps"&gt;ICML&lt;/span&gt;&amp;nbsp;slides:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WMD example from Matt's slides" src="https://vene.ro/images/wmd-obama.png"/&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Prepare-some-word-embeddings"&gt;Prepare some word embeddings&lt;a class="anchor-link" href="#Prepare-some-word-embeddings"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The key ingredient in &lt;span class="caps"&gt;WMD&lt;/span&gt; is a good distance measure between words.  Dense representations of words, also known by the trendier name &amp;#8220;word embeddings&amp;#8221; (because &amp;#8220;distributed word representations&amp;#8221; didn&amp;#8217;t stick), do the trick here.  We could train the embeddings ourselves, but for meaningful results we would need tons of documents, and that might take a while. So let&amp;#8217;s just use the ones from the &lt;a href="https://code.google.com/p/word2vec/"&gt;&lt;code&gt;word2vec&lt;/code&gt;&lt;/a&gt; team. &lt;a href="https://drive.google.com/file/d/0B7XkCwpI5KDYNlNUTTlSS21pQmM/edit?usp=sharing"&gt;(download&amp;nbsp;link)&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [1]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.datasets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fetch_20newsgroups&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.feature_extraction.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CountVectorizer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.cross_validation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;train_test_split&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [2]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data/embed.dat"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Caching word embeddings in memmapped format..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;gensim.models.word2vec&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Word2Vec&lt;/span&gt;
    &lt;span class="n"&gt;wv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Word2Vec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_word2vec_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"data/GoogleNews-vectors-negative300.bin.gz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data/embed.dat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'w+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;wv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syn0norm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;syn0norm&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data/embed.vocab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;voc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vocab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wv&lt;/span&gt;

&lt;span class="n"&gt;W&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data/embed.dat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data/embed.vocab"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;vocab_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [3]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;vocab_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vocab_list&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Reproducing-the-demo-above"&gt;Reproducing the demo above&lt;a class="anchor-link" href="#Reproducing-the-demo-above"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [33]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;d1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Obama speaks to the media in Illinois"&lt;/span&gt;
&lt;span class="n"&gt;d2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The President addresses the press in Chicago"&lt;/span&gt;

&lt;span class="n"&gt;vect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CountVectorizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stop_words&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"english"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;d1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Features:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_feature_names&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;Features: addresses, chicago, illinois, media, obama, president, press, speaks
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;The two documents are completely orthogonal in terms of&amp;nbsp;bag-of-words&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [71]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;scipy.spatial.distance&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cosine&lt;/span&gt;
&lt;span class="n"&gt;v_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;d1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;v_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toarray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;v_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toarray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cosine(doc_1, doc_2) = &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;[0 0 1 1 1 0 0 1] [1 1 0 0 0 1 1 0]
cosine(doc_1, doc_2) = 1.00
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [58]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;euclidean_distances&lt;/span&gt;
&lt;span class="n"&gt;W_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;vocab_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;vect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_feature_names&lt;/span&gt;&lt;span class="p"&gt;()]]&lt;/span&gt;
&lt;span class="n"&gt;D_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;euclidean_distances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;W_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"d(addresses, speaks) = &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"d(addresses, chicago) = &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;D_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;d(addresses, speaks) = 1.16
d(addresses, chicago) = 1.37
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;We will be using &lt;a href="https://github.com/wmayner/pyemd"&gt;&lt;code&gt;pyemd&lt;/code&gt;&lt;/a&gt;, a Python wrapper for &lt;a href="http://www.ariel.ac.il/sites/ofirpele/fastemd/"&gt;Pele and Werman&amp;#8217;s implementation of the earth mover&amp;#8217;s distance&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [74]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyemd&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;emd&lt;/span&gt;

&lt;span class="c1"&gt;# pyemd needs double precision input&lt;/span&gt;
&lt;span class="n"&gt;v_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v_1&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="n"&gt;v_1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;v_2&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="n"&gt;v_2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;D_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;D_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;D_&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="n"&gt;D_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# just for comparison purposes&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"d(doc_1, doc_2) = &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;D_&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;d(doc_1, doc_2) = 0.74
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Document-classification"&gt;Document classification&lt;a class="anchor-link" href="#Document-classification"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;We will use the &lt;a href="http://qwone.com/~jason/20Newsgroups/"&gt;&lt;em&gt;20 Newsgroups&lt;/em&gt;&lt;/a&gt; classification task.  Because &lt;span class="caps"&gt;WMD&lt;/span&gt; is an expensive computation, for this demo we just use a subset.  To emphasize the power of the method, we use a larger test size, but train on relatively few&amp;nbsp;samples.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [4]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;newsgroups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fetch_20newsgroups&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newsgroups&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newsgroups&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [5]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;docs_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docs_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_test_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                          &lt;span class="n"&gt;train_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                          &lt;span class="n"&gt;test_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                          &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Since&amp;nbsp;the &lt;code&gt;W&lt;/code&gt; embedding array is pretty huge, we might as well restrict it to just the words that actually occur in the&amp;nbsp;dataset.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [6]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;vect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CountVectorizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stop_words&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"english"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs_train&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;docs_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;common&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;vect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_feature_names&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;vocab_dict&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;W_common&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;W&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;vocab_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;We can then create a fixed-vocabulary vectorizer using only the words we have embeddings&amp;nbsp;for.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [7]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;vect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CountVectorizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vocabulary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;X_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;X_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;One way to proceed is to just pre-compute the pairwise distances between all documents, and use them to search for hyperparameters and evaluate the model. However, that would incur some extra computation, and &lt;span class="caps"&gt;WMD&lt;/span&gt; is expensive. Also, it&amp;#8217;s not the most pleasant user interface. So we define some scikit-learn compatible estimators for computing the &lt;span class="caps"&gt;WMD&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;WordMoversKNN&lt;/code&gt;&lt;/strong&gt; subclasses&amp;nbsp;from &lt;code&gt;KNeighborsClassifier&lt;/code&gt; and overrides&amp;nbsp;the &lt;code&gt;predict&lt;/code&gt; function to compute the &lt;span class="caps"&gt;WMD&lt;/span&gt; between all training and test&amp;nbsp;samples.&lt;/p&gt;
&lt;p&gt;In practice, however, we often don&amp;#8217;t know what is the&amp;nbsp;best &lt;code&gt;n_neighbors&lt;/code&gt; to use.  Simply&amp;nbsp;wrapping &lt;code&gt;WordMoversKNN&lt;/code&gt; in&amp;nbsp;a &lt;code&gt;GridSearchCV&lt;/code&gt; would be rather expensive because of all the distances that would need to be recomputed for every value&amp;nbsp;of &lt;code&gt;n_neighbors&lt;/code&gt;. So we introduce &lt;strong&gt;&lt;code&gt;WordMoversKNNCV&lt;/code&gt;&lt;/strong&gt;, which, when fitted, performs &lt;em&gt;cross-validation&lt;/em&gt; to find the best value&amp;nbsp;of &lt;code&gt;n_neighbors&lt;/code&gt; (under any given evaluation metric), while only computing the &lt;span class="caps"&gt;WMD&lt;/span&gt; once per fold, and only across folds&amp;nbsp;(saving &lt;code&gt;n_folds * fold_size ** 2&lt;/code&gt; evaluations).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [16]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="sd"&gt;"""%%file word_movers_knn.py"""&lt;/span&gt;

&lt;span class="c1"&gt;# Authors: Vlad Niculae, Matt Kusner&lt;/span&gt;
&lt;span class="c1"&gt;# License: Simplified BSD&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;euclidean_distances&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.externals.joblib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delayed&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.neighbors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KNeighborsClassifier&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;check_array&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.cross_validation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;check_cv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.metrics.scorer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;check_scoring&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.preprocessing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyemd&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;emd&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WordMoversKNN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KNeighborsClassifier&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""K nearest neighbors classifier using the Word Mover's Distance.&lt;/span&gt;

&lt;span class="sd"&gt;    Parameters&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    &lt;/span&gt;
&lt;span class="sd"&gt;    W_embed : array, shape: (vocab_size, embed_size)&lt;/span&gt;
&lt;span class="sd"&gt;        Precomputed word embeddings between vocabulary items.&lt;/span&gt;
&lt;span class="sd"&gt;        Row indices should correspond to the columns in the bag-of-words input.&lt;/span&gt;

&lt;span class="sd"&gt;    n_neighbors : int, optional (default = 5)&lt;/span&gt;
&lt;span class="sd"&gt;        Number of neighbors to use by default for :meth:`k_neighbors` queries.&lt;/span&gt;

&lt;span class="sd"&gt;    n_jobs : int, optional (default = 1)&lt;/span&gt;
&lt;span class="sd"&gt;        The number of parallel jobs to run for Word Mover's Distance computation.&lt;/span&gt;
&lt;span class="sd"&gt;        If ``-1``, then the number of jobs is set to the number of CPU cores.&lt;/span&gt;
&lt;span class="sd"&gt;    &lt;/span&gt;
&lt;span class="sd"&gt;    verbose : int, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Controls the verbosity; the higher, the more messages. Defaults to 0.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;    References&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    &lt;/span&gt;
&lt;span class="sd"&gt;    Matt J. Kusner, Yu Sun, Nicholas I. Kolkin, Kilian Q. Weinberger&lt;/span&gt;
&lt;span class="sd"&gt;    From Word Embeddings To Document Distances&lt;/span&gt;
&lt;span class="sd"&gt;    The International Conference on Machine Learning (ICML), 2015&lt;/span&gt;
&lt;span class="sd"&gt;    http://mkusner.github.io/publications/WMD.pdf&lt;/span&gt;
&lt;span class="sd"&gt;    &lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;
    &lt;span class="n"&gt;_pairwise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;W_embed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_neighbors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;W_embed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;W_embed&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WordMoversKNN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_neighbors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_neighbors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                            &lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'precomputed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'brute'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_wmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Compute the WMD between training sample i and given test row.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        Assumes that `row` and train samples are sparse BOW vectors summing to 1.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="n"&gt;union_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;union1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;W_minimal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;W_embed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;union_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;W_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;euclidean_distances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;W_minimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;bow_i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;union_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;bow_j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="n"&gt;union_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;emd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bow_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bow_j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;W_dist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_wmd_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Wrapper to compute the WMD of a row with all training samples.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        Assumes that `row` and train samples are sparse BOW vectors summing to 1.&lt;/span&gt;
&lt;span class="sd"&gt;        Useful for parallelization.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="n"&gt;n_samples_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_wmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_samples_train&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_pairwise_wmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Computes the word mover's distance between all train and test points.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        Parallelized over rows of X_test.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        Assumes that train and test samples are sparse BOW vectors summing to 1.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        Parameters&lt;/span&gt;
&lt;span class="sd"&gt;        ----------&lt;/span&gt;
&lt;span class="sd"&gt;        X_test: scipy.sparse matrix, shape: (n_test_samples, vocab_size)&lt;/span&gt;
&lt;span class="sd"&gt;            Test samples.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        X_train: scipy.sparse matrix, shape: (n_train_samples, vocab_size)&lt;/span&gt;
&lt;span class="sd"&gt;            Training samples. If `None`, uses the samples the estimator was fit with.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        Returns&lt;/span&gt;
&lt;span class="sd"&gt;        -------&lt;/span&gt;
&lt;span class="sd"&gt;        dist : array, shape: (n_test_samples, n_train_samples)&lt;/span&gt;
&lt;span class="sd"&gt;            Distances between all test samples and all train samples.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="n"&gt;n_samples_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;X_train&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_fit_X&lt;/span&gt;

        &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;
            &lt;span class="n"&gt;delayed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_wmd_row&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;test_sample&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;test_sample&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Fit the model using X as training data and y as target values&lt;/span&gt;

&lt;span class="sd"&gt;        Parameters&lt;/span&gt;
&lt;span class="sd"&gt;        ----------&lt;/span&gt;
&lt;span class="sd"&gt;        X : scipy sparse matrix, shape: (n_samples, n_features)&lt;/span&gt;
&lt;span class="sd"&gt;            Training data. &lt;/span&gt;

&lt;span class="sd"&gt;        y : {array-like, sparse matrix}&lt;/span&gt;
&lt;span class="sd"&gt;            Target values of shape = [n_samples] or [n_samples, n_outputs]&lt;/span&gt;

&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accept_sparse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'csr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'l1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WordMoversKNN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Predict the class labels for the provided data&lt;/span&gt;
&lt;span class="sd"&gt;        Parameters&lt;/span&gt;
&lt;span class="sd"&gt;        ----------&lt;/span&gt;
&lt;span class="sd"&gt;        X : scipy.sparse matrix, shape (n_test_samples, vocab_size)&lt;/span&gt;
&lt;span class="sd"&gt;            Test samples.&lt;/span&gt;

&lt;span class="sd"&gt;        Returns&lt;/span&gt;
&lt;span class="sd"&gt;        -------&lt;/span&gt;
&lt;span class="sd"&gt;        y : array of shape [n_samples]&lt;/span&gt;
&lt;span class="sd"&gt;            Class labels for each data sample.&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accept_sparse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'csr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'l1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_pairwise_wmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WordMoversKNN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    
    
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WordMoversKNNCV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WordMoversKNN&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Cross-validated KNN classifier using the Word Mover's Distance.&lt;/span&gt;

&lt;span class="sd"&gt;    Parameters&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    W_embed : array, shape: (vocab_size, embed_size)&lt;/span&gt;
&lt;span class="sd"&gt;        Precomputed word embeddings between vocabulary items.&lt;/span&gt;
&lt;span class="sd"&gt;        Row indices should correspond to the columns in the bag-of-words input.&lt;/span&gt;

&lt;span class="sd"&gt;    n_neighbors_try : sequence, optional&lt;/span&gt;
&lt;span class="sd"&gt;        List of ``n_neighbors`` values to try.&lt;/span&gt;
&lt;span class="sd"&gt;        If None, tries 1-5 neighbors.&lt;/span&gt;

&lt;span class="sd"&gt;    scoring : string, callable or None, optional, default: None&lt;/span&gt;
&lt;span class="sd"&gt;        A string (see model evaluation documentation) or&lt;/span&gt;
&lt;span class="sd"&gt;        a scorer callable object / function with signature&lt;/span&gt;
&lt;span class="sd"&gt;        ``scorer(estimator, X, y)``.&lt;/span&gt;

&lt;span class="sd"&gt;    cv : int, cross-validation generator or an iterable, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Determines the cross-validation splitting strategy.&lt;/span&gt;
&lt;span class="sd"&gt;        Possible inputs for cv are:&lt;/span&gt;
&lt;span class="sd"&gt;          - None, to use the default 3-fold cross-validation,&lt;/span&gt;
&lt;span class="sd"&gt;          - integer, to specify the number of folds.&lt;/span&gt;
&lt;span class="sd"&gt;          - An object to be used as a cross-validation generator.&lt;/span&gt;
&lt;span class="sd"&gt;          - An iterable yielding train/test splits.&lt;/span&gt;
&lt;span class="sd"&gt;        For integer/None inputs, StratifiedKFold is used.&lt;/span&gt;

&lt;span class="sd"&gt;    n_jobs : int, optional (default = 1)&lt;/span&gt;
&lt;span class="sd"&gt;        The number of parallel jobs to run for Word Mover's Distance computation.&lt;/span&gt;
&lt;span class="sd"&gt;        If ``-1``, then the number of jobs is set to the number of CPU cores.&lt;/span&gt;

&lt;span class="sd"&gt;    verbose : int, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Controls the verbosity; the higher, the more messages. Defaults to 0.&lt;/span&gt;

&lt;span class="sd"&gt;    Attributes&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    cv_scores_ : array, shape (n_folds, len(n_neighbors_try))&lt;/span&gt;
&lt;span class="sd"&gt;        Test set scores for each fold.&lt;/span&gt;

&lt;span class="sd"&gt;    n_neighbors_ : int,&lt;/span&gt;
&lt;span class="sd"&gt;        The best `n_neighbors` value found.&lt;/span&gt;

&lt;span class="sd"&gt;    References&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;

&lt;span class="sd"&gt;    Matt J. Kusner, Yu Sun, Nicholas I. Kolkin, Kilian Q. Weinberger&lt;/span&gt;
&lt;span class="sd"&gt;    From Word Embeddings To Document Distances&lt;/span&gt;
&lt;span class="sd"&gt;    The International Conference on Machine Learning (ICML), 2015&lt;/span&gt;
&lt;span class="sd"&gt;    http://mkusner.github.io/publications/WMD.pdf&lt;/span&gt;
&lt;span class="sd"&gt;    &lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;W_embed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_neighbors_try&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scoring&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_neighbors_try&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n_neighbors_try&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scoring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scoring&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WordMoversKNNCV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;W_embed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                              &lt;span class="n"&gt;n_neighbors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                              &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                              &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Fit KNN model by choosing the best `n_neighbors`.&lt;/span&gt;
&lt;span class="sd"&gt;        &lt;/span&gt;
&lt;span class="sd"&gt;        Parameters&lt;/span&gt;
&lt;span class="sd"&gt;        -----------&lt;/span&gt;
&lt;span class="sd"&gt;        X : scipy.sparse matrix, (n_samples, vocab_size)&lt;/span&gt;
&lt;span class="sd"&gt;            Data&lt;/span&gt;
&lt;span class="sd"&gt;        y : ndarray, shape (n_samples,) or (n_samples, n_targets)&lt;/span&gt;
&lt;span class="sd"&gt;            Target&lt;/span&gt;
&lt;span class="sd"&gt;        """&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_neighbors_try&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;n_neighbors_try&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;n_neighbors_try&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_neighbors_try&lt;/span&gt;

        &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accept_sparse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'csr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'l1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_cv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;knn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KNeighborsClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'precomputed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'brute'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;scorer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_scoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scoring&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scoring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;train_ix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_ix&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_pairwise_wmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;test_ix&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_ix&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_ix&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;train_ix&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="n"&gt;scorer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_neighbors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;test_ix&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;n_neighbors_try&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cv_scores_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;

        &lt;span class="n"&gt;best_k_ix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;best_k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n_neighbors_try&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;best_k_ix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_neighbors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_neighbors_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;best_k&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WordMoversKNNCV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;Overwriting word_movers_knn.py
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [10]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;knn_cv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WordMoversKNNCV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;n_neighbors_try&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                         &lt;span class="n"&gt;W_embed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;W_common&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_jobs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;knn_cv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="application/vnd.jupyter.stderr"&gt;
&lt;pre&gt;[Parallel(n_jobs=3)]: Done  12 tasks      | elapsed:   30.8s
[Parallel(n_jobs=3)]: Done  34 out of  34 | elapsed:  2.0min finished
[Parallel(n_jobs=3)]: Done  12 tasks      | elapsed:   25.7s
[Parallel(n_jobs=3)]: Done  33 out of  33 | elapsed:  2.9min finished
[Parallel(n_jobs=3)]: Done  12 tasks      | elapsed:   53.3s
[Parallel(n_jobs=3)]: Done  33 out of  33 | elapsed:  2.0min finished
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[10]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain"&gt;
&lt;pre&gt;WordMoversKNNCV(W_embed=memmap([[ 0.04283, -0.01124, ..., -0.05679, -0.00763],
       [ 0.02884, -0.05923, ..., -0.04744,  0.06698],
       ...,
       [ 0.08428, -0.15534, ..., -0.01413,  0.04561],
       [-0.02052,  0.08666, ...,  0.03659,  0.10445]]),
        cv=3, n_jobs=3, n_neighbors_try=range(1, 20), scoring=None,
        verbose=5)&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [11]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CV score: &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knn_cv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cv_scores_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;CV score: 0.38
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [12]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Test score: &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knn_cv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_test&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="application/vnd.jupyter.stderr"&gt;
&lt;pre&gt;[Parallel(n_jobs=3)]: Done  12 tasks      | elapsed:   32.2s
[Parallel(n_jobs=3)]: Done  66 tasks      | elapsed:  4.3min
[Parallel(n_jobs=3)]: Done 156 tasks      | elapsed: 12.5min
[Parallel(n_jobs=3)]: Done 282 tasks      | elapsed: 30.5min
[Parallel(n_jobs=3)]: Done 300 out of 300 | elapsed: 48.9min finished
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;Test score: 0.31
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Comparison-with-other-models"&gt;Comparison with other models&lt;a class="anchor-link" href="#Comparison-with-other-models"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Now let&amp;#8217;s see how &lt;span class="caps"&gt;WMD&lt;/span&gt; compares with some common approaches, on bag of words features.  The most apples-to-apples comparison would be
K nearest neighbors with a cosine similarity metric. This approach performs worse than using &lt;span class="caps"&gt;WMD&lt;/span&gt;. (All scores are&amp;nbsp;accuracies.)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [13]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.svm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LinearSVC&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.neighbors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KNeighborsClassifier&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sklearn.grid_search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GridSearchCV&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [21]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;knn_grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GridSearchCV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KNeighborsClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'cosine'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;algorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'brute'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_neighbors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
                        &lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;knn_grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CV score: &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knn_grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;best_score_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Test score: &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knn_grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_test&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;CV score: 0.34
Test score: 0.22
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Another common method for text classification is the linear support vector machine on bag of words.
This performs a bit better than vanilla cosine &lt;span class="caps"&gt;KNN&lt;/span&gt;, but worse than using &lt;span class="caps"&gt;WMD&lt;/span&gt; in this setting.  In our experience,
this seems to depend on the amount of training data&amp;nbsp;available.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [17]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;svc_grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GridSearchCV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LinearSVC&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                        &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                        &lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;svc_grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CV score: &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;svc_grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;best_score_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Test score: &lt;/span&gt;&lt;span class="si"&gt;{:.2f}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;svc_grid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X_test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_test&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;CV score: 0.35
Test score: 0.27
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="What-have-we-learned?"&gt;What have we learned?&lt;a class="anchor-link" href="#What-have-we-learned?"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;span class="caps"&gt;WMD&lt;/span&gt; is much better at capturing semantic similarity between documents than cosine, due to its ability to generalize to unseen words.  The &lt;span class="caps"&gt;SVM&lt;/span&gt; does somewhat better than cosine &lt;span class="caps"&gt;KNN&lt;/span&gt;, but still lacks such out-of-vocabulary generalization.   Given enough data, &lt;span class="caps"&gt;WMD&lt;/span&gt; can probably improve this margin, especially using something like metric learning on&amp;nbsp;top.&lt;/p&gt;
&lt;p&gt;The exact &lt;span class="caps"&gt;WMD&lt;/span&gt;, as we have used it here, is pretty slow.  This code is not optimized as much as it could be, there is potential through caching and using Cython.
However, a major limitation remains the cost of actually computing the &lt;span class="caps"&gt;EMD&lt;/span&gt;. To scale even higher, exactness can be relaxed by using lower bounds. In our next post, we will compare such optimization strategies, as discussed in &lt;a href="http://mkusner.github.io/publications/WMD.pdf"&gt;the &lt;span class="caps"&gt;WMD&lt;/span&gt; paper&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = '//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
    mathjaxscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'AMS' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: 'center'," +
        "    displayIndent: '0em'," +
        "    showMathMenu: true," +
        "    tex2jax: { " +
        "        inlineMath: [ ['$','$'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        " linebreaks: { automatic: true, width: '95% container' }, " +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'black ! important'} }" +
        "    } " +
        "}); ";
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;
</content><category term="python"/><category term="word embeddings"/><category term="text classification"/><category term="earth mover's distance"/></entry><entry><title>Flask-SocketIO on OpenShift: fallback on another port</title><link href="//vene.ro/blog/flask-socketio-openshift-fallback-xhr-polling.html" rel="alternate"/><published>2015-07-06T00:00:00+02:00</published><updated>2015-07-06T00:00:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2015-07-06:/blog/flask-socketio-openshift-fallback-xhr-polling.html</id><summary type="html">&lt;h2 id="tl-dr"&gt;&lt;span class="caps"&gt;TL&lt;/span&gt;; &lt;span class="caps"&gt;DR&lt;/span&gt;&lt;a class="headerlink" href="#tl-dr" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hacked the &lt;a href="https://gist.github.com/vene/c0657d854ae74a4511d2"&gt;SocketIO client
0.9.16&lt;/a&gt; to support
specifying a special port&amp;nbsp;(&lt;code&gt;wsport&lt;/code&gt;) to use only for the WebSocket protocol,
while keeping all other traffic on the default port. This is required by setups
such as OpenShift which require WebSocket traffic to come over a different …&lt;/p&gt;</summary><content type="html">&lt;h2 id="tl-dr"&gt;&lt;span class="caps"&gt;TL&lt;/span&gt;; &lt;span class="caps"&gt;DR&lt;/span&gt;&lt;a class="headerlink" href="#tl-dr" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hacked the &lt;a href="https://gist.github.com/vene/c0657d854ae74a4511d2"&gt;SocketIO client
0.9.16&lt;/a&gt; to support
specifying a special port&amp;nbsp;(&lt;code&gt;wsport&lt;/code&gt;) to use only for the WebSocket protocol,
while keeping all other traffic on the default port. This is required by setups
such as OpenShift which require WebSocket traffic to come over a different port&amp;nbsp;(say &lt;code&gt;8000&lt;/code&gt; rather&amp;nbsp;than &lt;code&gt;80&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="the-current-state-of-affairs"&gt;The current state of affairs&lt;a class="headerlink" href="#the-current-state-of-affairs" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;#8217;ve been trying to host an interactive web app with WebSockets on
&lt;a href="https://www.openshift.com/"&gt;OpenShift&lt;/a&gt;. Since I&amp;#8217;m a poor student and this
is a research app, I wanted a reasonably powerful free hosting&amp;nbsp;option.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.openshift.com/paas-websockets/"&gt;OpenShift added WebSockets support over 2 years
ago.&lt;/a&gt; But because of some
internal limitations, the WebSocket traffic needs to go over different ports
than usual&amp;nbsp;(&lt;code&gt;8000&lt;/code&gt; for unsecured&amp;nbsp;and &lt;code&gt;8443&lt;/code&gt; for secured&amp;nbsp;connections).&lt;/p&gt;
&lt;p&gt;&lt;a href="http://socket.io/"&gt;SocketIO&lt;/a&gt; is a cool library that allows event-driven
bidirectional traffic in web apps.  It tries to use WebSockets if available,
and falls back to other transport protocols (such as &lt;a href="https://en.wikipedia.org/wiki/Push_technology#Long_polling"&gt;&lt;span class="caps"&gt;XHR&lt;/span&gt; Long
Polling&lt;/a&gt;)&amp;nbsp;otherwise.&lt;/p&gt;
&lt;p&gt;Oh and to make matters worse, I&amp;#8217;m using Flask for this web app, and currently
the best way to use SocketIO from Flask is with the &lt;a href="https://flask-socketio.readthedocs.org/en/latest/"&gt;Flask-SocketIO&lt;/a&gt; +
&lt;a href="http://gevent-socketio.readthedocs.org/"&gt;gevent-socketio&lt;/a&gt; combo, which is,
alas, incompatible with recent versions of SocketIO. According to the
community, the most stable release is SocketIO&amp;nbsp;0.9.16.&lt;/p&gt;
&lt;h2 id="the-problem-in-a-nutshell"&gt;The problem in a nutshell&lt;a class="headerlink" href="#the-problem-in-a-nutshell" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When debugging my app locally, I&amp;#8217;d connect with something&amp;nbsp;like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;:&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/chat&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When deploying on OpenShift,&amp;nbsp;the &lt;code&gt;location.port&lt;/code&gt; is now different from the
WebSocket port, so I used a Flask config that determines server-side where it&amp;#8217;s
running&amp;nbsp;from:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wsport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{{ config[&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;WEBSOCKET_PORT&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;] }}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The app now runs great both when debugging locally or on OpenShift, except if
for some reason the WebSocket connection fails (either if the browser is old
or because of strict firewalls).  This happens because long polling uses normal
&lt;span class="caps"&gt;HTTP&lt;/span&gt; requests and should be done over the default port, rather than the
OpenShift-specific&amp;nbsp;one.&lt;/p&gt;
&lt;h2 id="making-things-work"&gt;Making things work&lt;a class="headerlink" href="#making-things-work" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My first thought was to work around it by &lt;a href="http://stackoverflow.com/questions/8588689/node-js-socket-io-client-connect-failed-event"&gt;manually implementing the fallback
client-side&lt;/a&gt;.  This, surprisingly, failed: it turns out that
SocketIO 0.9.16 first attempts a handshake to find out what transports the
server supports. If the initial connection is on&amp;nbsp;port &lt;code&gt;8000&lt;/code&gt;, then the
handshake will be attempted also on&amp;nbsp;port &lt;code&gt;8000&lt;/code&gt;, so&amp;nbsp;the &lt;code&gt;connect_failed&lt;/code&gt; event
won&amp;#8217;t even be triggered.  The second cleanest solution I could think of was to
add another parameter to the SocketIO&amp;nbsp;options, &lt;code&gt;wsport&lt;/code&gt;, allowing the user to
specify a different port over which to do WebSocket connections, while keeping
all other traffic over the default port.  This works like a&amp;nbsp;charm!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gist.github.com/vene/c0657d854ae74a4511d2"&gt;My updated SocketIO client is available as a
gist&lt;/a&gt;, and the client-side
connection code looks like&amp;nbsp;this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/chat&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;wsport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wsport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;connect timeout&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I had to reduce the connection timeout, for a better user experience.  I&amp;#8217;m way
out of my comfort zone here, so in case I missed a better solution, do let me&amp;nbsp;know!&lt;/p&gt;</content><category term="flask"/><category term="flask-socketio"/><category term="flask"/><category term="socketio"/><category term="openshift"/></entry><entry><title>Kemeny-Young Optimal Rank Aggregation in Python</title><link href="//vene.ro/blog/kemeny-young-optimal-rank-aggregation-in-python.html" rel="alternate"/><published>2014-01-22T18:00:00+01:00</published><updated>2014-01-22T18:00:00+01:00</updated><author><name>vene</name></author><id>tag:vene.ro,2014-01-22:/blog/kemeny-young-optimal-rank-aggregation-in-python.html</id><summary type="html">&lt;p&gt;Rank aggregation is a problem with many important applications and naive approaches to it go wrong in subtle ways. Let&amp;#8217;s say that your national Quidditch league is dominated by five major wizard sports newspapers. Yes, the ones with moving images and everything.  Every week after the games, each of them publishes a ranking of the star players.  For now, let&amp;#8217;s suppose that the set of players under investigation is always the same, as the problem becomes a bit more complicated otherwise.&amp;nbsp;&amp;#8230;&lt;/p&gt;</summary><content type="html">&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=87a9b031"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Rank aggregation is a problem with many important applications and naive approaches to it go wrong in subtle&amp;nbsp;ways.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s say that your national Quidditch league is dominated by five major wizard sports newspapers. Yes, the ones with moving images and everything.  Every week after the games, each of them publishes a ranking of the star players.  For now, let&amp;#8217;s suppose that the set of players under investigation is always the same, as the problem becomes a bit more complicated&amp;nbsp;otherwise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Athletic Wizard&lt;/strong&gt;: Alicia Spinnet, Ginny Weasley, Gwendolyn Morgan, Robin Higgy, Debbie Muntz &lt;br/&gt;
&lt;strong&gt;The Daily Prophet&lt;/strong&gt;: Alicia, Ginny, Robin, Gwendolyn, Debbie &lt;br/&gt;
&lt;strong&gt;Quidditch News&lt;/strong&gt;: Robin, Ginny, Gwendolyn, Debbie, Alicia &lt;br/&gt;
&lt;strong&gt;Seeker Weekly&lt;/strong&gt;: Gwendolyn, Ginny, Robin, Debbie, Alicia &lt;br/&gt;
&lt;strong&gt;The Quibbler&lt;/strong&gt;: Debbie, Ginny, Robin, Gwendolyn, Alicia &lt;br/&gt;&lt;/p&gt;
&lt;p&gt;As you can see, there&amp;#8217;s quite a bit of disagreement and personal taste involved. You didn&amp;#8217;t get to watch all of the games, but you&amp;#8217;d like to make a decision on who the best players were, by somehow aggregating the opinions of the popular newspapers.  An easy option would be to pretend that each newspaper &lt;em&gt;votes&lt;/em&gt; for the player that they rank #1, and ignore the rest.  As Alicia Spinnet is the only player getting &lt;em&gt;two&lt;/em&gt; nominations for best player, she should win best player,&amp;nbsp;right?&lt;/p&gt;
&lt;p&gt;Upon closer inspection, Alicia seems very controversial, loved by two but hated by five of the newspaper.  Ginny, on the other hand, didn&amp;#8217;t stand out as best player to anybody, but she was &lt;em&gt;uniformly&lt;/em&gt; considered runner-up.  There should be some way to account for this. It would be nice if we would have a method of finding an &lt;em&gt;optimal&lt;/em&gt; ranking that maximizes some sort of agreement with the opinions we are trying to&amp;nbsp;aggregate.&lt;/p&gt;
&lt;h2 id="Kendall's-Tau-distance"&gt;Kendall&amp;#8217;s Tau distance&lt;a class="anchor-link" href="#Kendall's-Tau-distance"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;One of the most interesting ways to measure disagreement between rankings is the Tau statistic introduced by Kendall.  It essentially measures the number of pairwise disagreements between two rankings.  Since you can think of it as the number of flips you need to perform on a ranking to turn it into the other, it is sometimes called &lt;em&gt;bubble-sort distance&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;While the closely-related Tau correlation coefficient is implemented in Scipy as &lt;a href="http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.kendalltau.html"&gt;&lt;code&gt;scipy.stats.kendalltau&lt;/code&gt;&lt;/a&gt;, let&amp;#8217;s code it ourselves in a simpler&amp;nbsp;way.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=55907dbb"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [1]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;itertools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;combinations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;permutations&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;kendalltau_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rank_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rank_b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tau&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rank_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;combinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tau&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rank_a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rank_a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt;
                &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rank_b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rank_b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tau&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=da54e7a6"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Now we can represent a rank as a numpy vector, with missing&amp;nbsp;values.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=394de716"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [2]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# columns in order of appearance:&lt;/span&gt;
&lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Alicia Ginny Gwendolyn Robin Debbie"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ranks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=b79b0d8b"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Two rankings that agree completely should have a distance of&amp;nbsp;0.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=4a74f491"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [3]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;kendalltau_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[3]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain"&gt;
&lt;pre&gt;0&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=67bd4720"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;The Athletic Wizard seems to be closer to the Daily Prophet than to Seeker&amp;nbsp;Weekly.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=f60d75c3"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [4]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d(athletic_wizard, prophet) = &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kendalltau_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'d(athletic_wizard, seeker) = &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kendalltau_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;d(athletic_wizard, prophet) = 1
d(athletic_wizard, seeker) = 5
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=8a3334d9"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Kemeny-Young-rank-aggregation"&gt;Kemeny-Young rank aggregation&lt;a class="anchor-link" href="#Kemeny-Young-rank-aggregation"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Now that we have a distance metric, we can formulate a loss function to minimize in rank-space.
We are looking for a ranking $\hat\tau$ that satisfies
$$ \sum_i d(\hat\tau, \tau_i) \leq \sum_i d(\tau, \tau_i) \text{ for all } \tau&amp;nbsp;$$&lt;/p&gt;
&lt;p&gt;This aggregation method was proposed by John Kemeny [&lt;a href="#note1"&gt;1&lt;/a&gt;], and later shown by Peyton Young to be a maximum likelihood estimator of pairwise preferences under the assumption that a voter will randomly flip two candidates from the &lt;em&gt;true&lt;/em&gt; ranking is the same $p &amp;lt; 0.5$ [&lt;a href="#note2"&gt;2&lt;/a&gt;].&lt;/p&gt;
&lt;p&gt;For rankings of small length, one way to compute this optimal aggregation is by comparing the scores of all possible rankings, in other words, a &lt;strong&gt;brute-force&lt;/strong&gt;&amp;nbsp;approach:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=946110e6"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [5]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rankaggr_brute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;min_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inf&lt;/span&gt;
    &lt;span class="n"&gt;best_rank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;n_voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;candidate_rank&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;permutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kendalltau_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidate_rank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;min_dist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;min_dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt;
            &lt;span class="n"&gt;best_rank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;candidate_rank&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;min_dist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;best_rank&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=619f3d79"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [6]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aggr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rankaggr_brute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"A Kemeny-Young aggregation with score &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt; is: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argsort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aggr&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;A Kemeny-Young aggregation with score 15 is: Ginny, Robin, Gwendolyn, Debbie, Alicia
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=91928067"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Integer-Programming-formulation"&gt;Integer Programming formulation&lt;a class="anchor-link" href="#Integer-Programming-formulation"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The brute-force approach is, as you can see, simple to understand and quick to code. However, the number of total ranks of size $n$ is $n!$, so this approach quickly becomes infeasible for real-world problems.  Unfortunately, it turns out that this problem (along with many related problems in rank-world) is &lt;span class="caps"&gt;NP&lt;/span&gt;-hard even for only four voters [&lt;a href="#note3"&gt;3&lt;/a&gt;]. There have been quite a few approaches and approximations. In many cases, such as search results aggregation, approximations are good enough.  For the cases where an exact solution is required, a formulation as a constrained integer program is given in [&lt;a href="#note4"&gt;4&lt;/a&gt;].&lt;/p&gt;
&lt;p&gt;We build a weighted directed graph $G = (V, E)$ with the candidates as vertices.  The edges are defined as such: for every pair of candidates $i, j$, let $\#\{i &amp;gt; j\}$ denote the number of voters who rank $i$ higher than $j$.  Draw an edge $e$ between each $i, j$ with weight $w_e = |\#\{i &amp;gt; j\} - \#\{j &amp;gt; i\}|$ (if nonzero, of course). The orientation of the edge is from the less preferred to the more preferred&amp;nbsp;node.&lt;/p&gt;
&lt;p&gt;The formulation is based on the alternative interpretation of the Kemeny optimal aggregation as the ranking that minimizes the weights of edges it disagrees&amp;nbsp;with:&lt;/p&gt;
&lt;p&gt;$$
\operatorname{minimize} \sum_{e \in E} w_e x_e &amp;#92;
\text{subject to} &amp;#92;
\forall i \neq j \in V, x_{ij} + x_{ji} = 1 &amp;#92;
\forall i \neq j \neq k \neq i \in V, x_{ij} + x_{jk} + x_{ki} \geq 1&amp;nbsp;$$&lt;/p&gt;
&lt;p&gt;In the above problem, all variables are integer, and effectively binary under the other constraints.  The interpretation is that $x_{ij} = 1$ if, in the aggregated rank, $i$ is ranked lower than&amp;nbsp;$j$.&lt;/p&gt;
&lt;p&gt;The constraints essentially impose that the variables define a total order. The first set of constraints enforce antisymmetry and totality: either $i$ is ranked lower than $j$ or the other way around. The second set of constraints enforce&amp;nbsp;transitivity.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=9d9d87ed"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [7]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_build_graph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;n_voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;
    &lt;span class="n"&gt;edge_weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_candidates&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;combinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;preference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;h_ij&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;preference&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# prefers i to j&lt;/span&gt;
        &lt;span class="n"&gt;h_ji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;preference&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# prefers j to i&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;h_ij&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;h_ji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;edge_weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h_ij&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;h_ji&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;h_ij&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;h_ji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;edge_weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h_ji&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;h_ij&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;edge_weights&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_build_graph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;[[ 0.  0.  0.  0.  0.]
 [ 1.  0.  3.  3.  3.]
 [ 1.  0.  0.  0.  3.]
 [ 1.  0.  1.  0.  3.]
 [ 1.  0.  0.  0.  0.]]
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=e15b3944"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Now, let&amp;#8217;s solve the linear program with the &lt;span class="caps"&gt;LGPL&lt;/span&gt;-licensed &lt;a href="http://sourceforge.net/projects/lpsolve/"&gt;&lt;code&gt;lpsolve&lt;/code&gt;&lt;/a&gt;. It conveniently comes with a Python&amp;nbsp;interface.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=0ba4ff5e"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [8]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;lp_solve&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lp_solve&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rankaggr_lp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Kemeny-Young optimal rank aggregation"""&lt;/span&gt;

    &lt;span class="n"&gt;n_voters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;
    
    &lt;span class="c1"&gt;# maximize c.T * x&lt;/span&gt;
    &lt;span class="n"&gt;edge_weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_build_graph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;edge_weights&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ravel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  

    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;

    &lt;span class="c1"&gt;# constraints for every pair&lt;/span&gt;
    &lt;span class="n"&gt;pairwise_constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                     &lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pairwise_constraints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="n"&gt;combinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="c1"&gt;# and for every cycle of length 3&lt;/span&gt;
    &lt;span class="n"&gt;triangle_constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
                                     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                                     &lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;triangle_constraints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="n"&gt;permutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;pairwise_constraints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;triangle_constraints&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;constraint_rhs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pairwise_constraints&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                                &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;triangle_constraints&lt;/span&gt;&lt;span class="p"&gt;))])&lt;/span&gt;
    &lt;span class="n"&gt;constraint_signs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pairwise_constraints&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;  &lt;span class="c1"&gt;# ==&lt;/span&gt;
                                  &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;triangle_constraints&lt;/span&gt;&lt;span class="p"&gt;))])&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;gt;=&lt;/span&gt;

    &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lp_solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;constraint_rhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;constraint_signs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="n"&gt;xint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n_candidates&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;n_candidates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_candidates&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;aggr_rank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aggr_rank&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=955a0d66"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [9]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aggr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rankaggr_lp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kendalltau_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aggr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"A Kemeny-Young aggregation with score &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt; is: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;", "&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argsort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aggr&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain"&gt;
&lt;pre&gt;A Kemeny-Young aggregation with score 15 is: Ginny, Robin, Gwendolyn, Debbie, Alicia
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=37315a37"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;We get the same result as in the brute-force case.  However, it&amp;#8217;s much faster. Let&amp;#8217;s verify this, and in the process, also test that we always get the same&amp;nbsp;result.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=d6f24c26"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [10]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;timings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"method rank_len n_ranks time"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank_len&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n_ranks&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ranks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rank_len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_ranks&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;ranks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;brute_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;brute_aggr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rankaggr_brute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;timings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"brute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;rank_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rank_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;n_ranks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_ranks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                                 &lt;span class="n"&gt;ignore_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lp_aggr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rankaggr_lp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;timings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;rank_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rank_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;n_ranks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_ranks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                                 &lt;span class="n"&gt;ignore_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brute_score&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt;
                &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kendalltau_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lp_aggr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# lp is much faster, let's run it for longer rankings.&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rank_len&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n_ranks&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lp_aggr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rankaggr_lp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;timings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;rank_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rank_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;n_ranks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n_ranks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                                 &lt;span class="n"&gt;ignore_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=a952f0eb"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [11]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="k"&gt;matplotlib&lt;/span&gt; inline
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;seaborn&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;sns&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xticks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;subplot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_ranks&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'brute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lp'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;this_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timings&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;timings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n_ranks&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;n_ranks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
                            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;semilogy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this_rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rank_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;this_rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt; n=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_ranks&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"upper left"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suptitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Ranking size vs. log time to solve"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output"&gt;
&lt;img alt="No description has been provided for this image" class="" src="data:image/png;base64,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
"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=041280d3"&gt;
&lt;div class="jp-Cell-inputWrapper"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;You can see that the brute force approach is a bit worse than exponential, while the integer programming approach is more reasonable and also is less sensitive to the number of voters being aggregated.  We therefore have a reasonably fast efficient exact solution to the rank aggregation problem on small&amp;nbsp;datasets.&lt;/p&gt;
&lt;h3 id="References"&gt;References&lt;a class="anchor-link" href="#References"&gt;¶&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span id="note1"&gt; [1] J Kemeny. &lt;a href="http://www.jstor.org/stable/10.2307/20026529"&gt;Mathematics without numbers&lt;/a&gt;, 1959 &lt;br/&gt; &lt;/span&gt;
&lt;span id="note2"&gt; [2] &lt;span class="caps"&gt;HP&lt;/span&gt; Young. &lt;a href="http://www.jstor.org/stable/10.2307/1961757"&gt;Condorcet&amp;#8217;s theory of voting&lt;/a&gt;, 1988 &lt;br/&gt;
&lt;/span&gt;&lt;span id="note3"&gt;
[3] &lt;span class="caps"&gt;JJ&lt;/span&gt; Bartholdi &lt;span class="caps"&gt;III&lt;/span&gt;, &lt;span class="caps"&gt;CA&lt;/span&gt; Tovey, &lt;span class="caps"&gt;MA&lt;/span&gt; Trick. &lt;a href="http://link.springer.com/article/10.1007/BF00295861"&gt;The computational difficulty of manipulating an election&lt;/a&gt;, 1989 &lt;br/&gt;
&lt;/span&gt;&lt;span id="note4"&gt;
[4] V Conitzer, A Davenport, J Kalagnanam. &lt;a href="http://www.aaai.org/Papers/AAAI/2006/AAAI06-099.pdf"&gt;Improved bounds for computing Kemeny rankings&lt;/a&gt;,&amp;nbsp;2006
&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = '//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
    mathjaxscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'AMS' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: 'center'," +
        "    displayIndent: '0em'," +
        "    showMathMenu: true," +
        "    tex2jax: { " +
        "        inlineMath: [ ['$','$'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        " linebreaks: { automatic: true, width: '95% container' }, " +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'black ! important'} }" +
        "    } " +
        "}); ";
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;
</content><category term="python"/><category term="python"/><category term="rank aggregation"/><category term="kendall"/><category term="tau"/><category term="kemeny"/><category term="kemeny-young"/><category term="voting theory"/></entry><entry><title>Site move</title><link href="//vene.ro/blog/site-move.html" rel="alternate"/><published>2014-01-10T12:00:00+01:00</published><updated>2014-01-10T12:00:00+01:00</updated><author><name>Vlad</name></author><id>tag:vene.ro,2014-01-10:/blog/site-move.html</id><summary type="html">&lt;p&gt;I finally got around to moving my entire website, including the blog,
to &lt;a href="http://getpelican.com"&gt;Pelican&lt;/a&gt;.  I probably would have gotten away with it
too if it weren&amp;#8217;t for those meddling kids who hacked my friend&amp;#8217;s server and
convinced me that it&amp;#8217;s worth the effort to go&amp;nbsp;static.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I finally got around to moving my entire website, including the blog,
to &lt;a href="http://getpelican.com"&gt;Pelican&lt;/a&gt;.  I probably would have gotten away with it
too if it weren&amp;#8217;t for those meddling kids who hacked my friend&amp;#8217;s server and
convinced me that it&amp;#8217;s worth the effort to go&amp;nbsp;static.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It only took 5 hours!&lt;/strong&gt; I am more and more convinced that static websites
allow for a better streamlined workflow that does wonders for&amp;nbsp;productivity.&lt;/p&gt;
&lt;p&gt;The static part of my old website was made with Jekyll before, so I kept it
almost identical, except for fixing some childish &lt;span class="caps"&gt;CSS&lt;/span&gt; bugs.  The same self-hate
that comes with reading code that you wrote yourself years ago also comes from
markup,&amp;nbsp;apparently.&lt;/p&gt;
&lt;p&gt;The blog, however, was migrated thanks to Pelican&amp;#8217;s Wordpress importer.  This
means that the images are missing, the comments are missing (though I think
they can be imported somehow) and the plugin-specific syntax such as Latex
and source code is poorly rendered.  Luckily the images are there in the
database dump, but I will need to go through the posts one by one to fix
everything.  For now, my priority was to get it up and running and keep as many
of the old links as&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;Unfortunately &lt;a href="http://pages.github.com"&gt;GitHub Pages&lt;/a&gt; doesn&amp;#8217;t allow &lt;span class="caps"&gt;URL&lt;/span&gt;
rewriting, but I use &lt;a href="https://cloudflare.com"&gt;CloudFlare&lt;/a&gt; for &lt;span class="caps"&gt;DNS&lt;/span&gt; and their
free plan gives me the right to use three forwarding rules.  With just 3 rules
I couldn&amp;#8217;t save all of the old URLs, so I had to prioritize something.  I
decided to rescue the links pointing directly to articles, which needed two
rules, for links with and without trailing slashes, because the CloudFlare
syntax has no way of specifying optional characters.  I used the third wish
that the genie granted me to simply redirect the&amp;nbsp;subdomain
&lt;code&gt;blog.vene.ro&lt;/code&gt; to the appropriate&amp;nbsp;subfolder,
&lt;code&gt;vene.ro/blog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Best part about this is that I will now be able to migrate my &lt;a href="/papers.html"&gt;personal list of
papers&lt;/a&gt; to
&lt;a href="https://github.com/vene/pelican-bibtex"&gt;pelican-bibtex&lt;/a&gt;.&lt;/p&gt;</content><category term="meta"/><category term="pelican"/><category term="blog"/></entry><entry><title>BibTeX-powered publications list for Pelican with pelican-bibtex</title><link href="//vene.ro/blog/bibtex-powered-publications-list-for-pelican-with-pelican-bibtex.html" rel="alternate"/><published>2013-04-22T10:45:00+02:00</published><updated>2013-04-22T10:45:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2013-04-22:/blog/bibtex-powered-publications-list-for-pelican-with-pelican-bibtex.html</id><summary type="html">&lt;h2 id="hook"&gt;Hook&lt;a class="headerlink" href="#hook" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Wouldn&amp;#8217;t you like to manage your academic publications list easily
within the context of your static website? Without resorting to external
services, or to software like &lt;em&gt;bibtex2html&lt;/em&gt;, which is very nice but will
then require restyling to fit your&amp;nbsp;templates?&lt;/p&gt;
&lt;p&gt;Look no more, with the help of &lt;a href="https://github.com/vene/pelican-bibtex"&gt;pelican-bibtex …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;h2 id="hook"&gt;Hook&lt;a class="headerlink" href="#hook" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Wouldn&amp;#8217;t you like to manage your academic publications list easily
within the context of your static website? Without resorting to external
services, or to software like &lt;em&gt;bibtex2html&lt;/em&gt;, which is very nice but will
then require restyling to fit your&amp;nbsp;templates?&lt;/p&gt;
&lt;p&gt;Look no more, with the help of &lt;a href="https://github.com/vene/pelican-bibtex"&gt;pelican-bibtex&lt;/a&gt; you can now manage
your papers from within&amp;nbsp;Pelican!&lt;/p&gt;
&lt;h2 id="backstory"&gt;Backstory&lt;a class="headerlink" href="#backstory" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At &lt;a href="http://fseoane.net"&gt;Fabian&lt;/a&gt;&amp;#8216;s advice, I started playing around with &lt;a href="http://getpelican.com"&gt;Pelican&lt;/a&gt;, a
static website/blog generator for Python. I like it better than the
other generators I used before, so I chose it the next time I had to set
up a website. I still didn&amp;#8217;t make the courage to migrate my current
website and blog to it, but I promise I&amp;nbsp;will.&lt;/p&gt;
&lt;p&gt;Pelican has a public plugins repository, but they have a license
constraint for all contributions. My plugin isn&amp;#8217;t complicated, but I had
to &amp;#8220;reverse engineer&amp;#8221; undocumented parts of the &lt;a href="http://pybtex.sourceforge.net"&gt;pybtex&lt;/a&gt; &lt;span class="caps"&gt;API&lt;/span&gt;. I think
that maybe that code that I used to render citations programatically can
be useful to others, so I don&amp;#8217;t want to release it under a restrictive
license. For this reason, I publish &lt;a href="https://github.com/vene/pelican-bibtex"&gt;pelican-bibtex&lt;/a&gt; in my personal
GitHub&amp;nbsp;account.&lt;/p&gt;
&lt;p&gt;You can see it in action in the &lt;a href="https://github.com/nlp-unibuc/nlp-unibuc-website/"&gt;source code&lt;/a&gt; for the website I am
working on at the moment, the home page of my research group. Example
output generated using pelican-bibtex can be seen &lt;a href="http://nlp-unibuc.github.io/publications.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="possible-extensions"&gt;Possible extensions&lt;a class="headerlink" href="#possible-extensions" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have not dug in too deeply but I believe this plugin can be extended,
with not much difficulty, to support referencing in Pelican blogs, and
render BibTeX references at the end of every post. This idea was
suggested by Avaris on #pelican, and I find it very cool. Since I don&amp;#8217;t
need this feature at the moment, it&amp;#8217;s not a priority, but it&amp;#8217;s something
that I would like to see at some&amp;nbsp;point.&lt;/p&gt;</content><category term="python"/><category term="bibtex"/><category term="blog"/><category term="citations"/><category term="pelican"/><category term="publications"/><category term="pybtex"/><category term="references"/><category term="static blog"/><category term="static website"/><category term="Uncategorized"/></entry><entry><title>Really the most common english idioms?</title><link href="//vene.ro/blog/really-most-common-english-idioms.html" rel="alternate"/><published>2013-02-11T16:50:00+01:00</published><updated>2013-02-11T16:50:00+01:00</updated><author><name>vene</name></author><id>tag:vene.ro,2013-02-11:/blog/really-most-common-english-idioms.html</id><summary type="html">&lt;p&gt;A while back I ran into &lt;a href="http://voxy.com/blog/index.php/2012/02/top-10-most-common-idioms-in-english/"&gt;this blog post&lt;/a&gt; and it made me wonder. I&amp;#8217;m
not a native speaker but the idiomatic phrases that they note as common
don&amp;#8217;t strike me as such. I don&amp;#8217;t think I have ever encountered them very
often in real&amp;nbsp;dialogue.&lt;/p&gt;
&lt;p&gt;The …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A while back I ran into &lt;a href="http://voxy.com/blog/index.php/2012/02/top-10-most-common-idioms-in-english/"&gt;this blog post&lt;/a&gt; and it made me wonder. I&amp;#8217;m
not a native speaker but the idiomatic phrases that they note as common
don&amp;#8217;t strike me as such. I don&amp;#8217;t think I have ever encountered them very
often in real&amp;nbsp;dialogue.&lt;/p&gt;
&lt;p&gt;The blog post lists the 10 most common idioms in English. &lt;strong&gt;Idioms&lt;/strong&gt;,
also known less ambiguously as &lt;strong&gt;fixed expressions&lt;/strong&gt;, are units of
language that span at least two words. Their meaning, relatively to the
individual meaning of the parts of the phrase, are figurative. Despite
this, fixed expressions don&amp;#8217;t classify as creative language, or
exploitations. By definition most speakers will unequivocally be
familiar with&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;For example, they cite &lt;em&gt;piece of cake&lt;/em&gt; as the most common idiomatic
expression. This refers to using the phrase to mean that something is
easy, that it isn&amp;#8217;t challenging. An example of literal use, however,
would be when ordering &lt;em&gt;a piece of cake&lt;/em&gt; for desert in a&amp;nbsp;restaurant.&lt;/p&gt;
&lt;p&gt;Everyone knows that language is a perpetually changing thing, so to
begin with it&amp;#8217;s even slightly misleading to discuss of the commonness of
a phrase, without giving more context. The blog post doesn&amp;#8217;t justify the
ranking with any numbers anyway, so let&amp;#8217;s take them one by one and find
out how common they really&amp;nbsp;are!&lt;/p&gt;
&lt;h2 id="corpus-linguistics"&gt;Corpus Linguistics&lt;a class="headerlink" href="#corpus-linguistics" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The approach we are taking here is known as corpus linguistics. The best
way to argue that a certain phrase is common, that something is used
with a specific meaning or that some constructions are normal is, under
corpus linguistics, not to make up examples that seem reasonable, but to
look at &lt;strong&gt;representative collections of text&lt;/strong&gt; (corpora) and trying to
find the examples there. The conclusions you get this way are backed by
real-world language&amp;nbsp;use.&lt;/p&gt;
&lt;p&gt;An argument often brought against generative linguistics is that it
focuses on the (hard) border between grammatical and not grammatical,
and the border is usually defined by made-up examples. This is
inappropriate for studying how the norms are exploited in real language
use, for example. I refer the interested to the work of &lt;a href="http://www.patrickhanks.com/"&gt;Patrick
Hanks&lt;/a&gt; [&lt;a href="#f1"&gt;1&lt;/a&gt;, &lt;a href="#f2"&gt;2&lt;/a&gt;].&lt;/p&gt;
&lt;p&gt;Corpus linguistics is sensitive to the corpus used. For this example
let&amp;#8217;s use two British English corpora: the &lt;a href="http://www.natcorp.ox.ac.uk/"&gt;British National Corpus&lt;/a&gt;
and the &lt;a href="http://oxforddictionaries.com/words/the-oxford-english-corpus"&gt;Oxford English Corpus&lt;/a&gt;. Measuring by number of words, the
latter is around 20 times bigger. The strong point of the &lt;span class="caps"&gt;BNC&lt;/span&gt; is the
attention given to the mixing proportions of various domains. The &lt;span class="caps"&gt;OEC&lt;/span&gt;,
on the other hand, is larger and more recent. I have a feeling (but I
cannot strongly affirm) that the differences in the following results
arise from the inclusion in the &lt;span class="caps"&gt;OEC&lt;/span&gt; of blogs dating from the&amp;nbsp;mid-2000s.&lt;/p&gt;
&lt;h2 id="cognitive-salience-vs-social-salience"&gt;Cognitive salience vs. social salience&lt;a class="headerlink" href="#cognitive-salience-vs-social-salience" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the key ideas that motivate corpus approaches is the mismatch
between these. The cognitive salience of something is the ease with
which we can recall it. An example often used in language is the fixed
expression &lt;em&gt;kicking the bucket&lt;/em&gt;. It is one of the standard examples of
fixed expressions that people give very often when asked. It is supposed
to mean &lt;em&gt;dying&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;However, big surprise: the &lt;span class="caps"&gt;BNC&lt;/span&gt; has only 18 instances of this phrase, out
of which only 3 are idiomatic, the rest being either literal or
metalinguistic. This is a nice example of the salience contrast, but we
mustn&amp;#8217;t hurry to conclusions. The &lt;span class="caps"&gt;OEC&lt;/span&gt; has 193 examples (still few,
relative to its size) but a lot more of them are idiomatic uses. To save
the time I didn&amp;#8217;t look at all the examples, but took a random sample of
size 18, to compare the relative frequencies to &lt;span class="caps"&gt;BNC&lt;/span&gt;. Here, 15 out of 18
instances are idiomatic and none are meta. Quite a&amp;nbsp;difference!&lt;/p&gt;
&lt;p&gt;This goes to show the importance of context when we draw conclusions
about language use. Now let&amp;#8217;s tackle the list with a similar&amp;nbsp;analysis.&lt;/p&gt;
&lt;h2 id="the-idioms"&gt;The idioms&lt;a class="headerlink" href="#the-idioms" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Piece of&amp;nbsp;cake&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In &lt;span class="caps"&gt;BNC&lt;/span&gt;, this phrase occurs 51 times. 29 of these occurrences,
however, the meaning is literal. In &lt;span class="caps"&gt;OEC&lt;/span&gt; we find 601 occurrences. In
a random sample of size 51 we find 12 literal&amp;nbsp;uses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Costing an arm and a&amp;nbsp;leg&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For flexibility we search for the phrase &lt;em&gt;an arm and a leg&lt;/em&gt;. In &lt;span class="caps"&gt;BNC&lt;/span&gt;
it can be found 29 times: one literal, four with the verb &lt;em&gt;to pay&lt;/em&gt;,
and 16 with &lt;em&gt;to cost&lt;/em&gt;. In &lt;span class="caps"&gt;OEC&lt;/span&gt; it appears 228 times. We take, again,
a sample of size 29 and find no literal uses, 16 with &lt;em&gt;to cost&lt;/em&gt;,
four with &lt;em&gt;to pay&lt;/em&gt;, three with &lt;em&gt;to charge&lt;/em&gt; and a few different uses.
The figurative meaning is the same in all cases: a lot of&amp;nbsp;money.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Break a leg&lt;/strong&gt;
    &lt;p&gt;
    &lt;span class="caps"&gt;BNC&lt;/span&gt;: 16, 13 of which are literal. &lt;span class="caps"&gt;OEC&lt;/span&gt;: 70 hits, 10/16&amp;nbsp;literal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hitting the&amp;nbsp;books&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;BNC&lt;/span&gt;: 1 occurrence of &lt;em&gt;hit the record books&lt;/em&gt;, which has a different
meaning. The idiom is never used. &lt;span class="caps"&gt;OEC&lt;/span&gt;: 135, one of which&amp;nbsp;literal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Letting the cat out of the&amp;nbsp;bag&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We just looked for cooccurrences of &lt;em&gt;cat&lt;/em&gt; in the context of the
phrase &lt;em&gt;out of the bag&lt;/em&gt;.&lt;br /&gt;
&lt;span class="caps"&gt;BNC&lt;/span&gt;: 19, out of which 3 metalinguistic/literal. &lt;span class="caps"&gt;OEC&lt;/span&gt;: 298, and out
of a sample of 19, all were&amp;nbsp;idiomatic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hitting the nail on the&amp;nbsp;head&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;BNC&lt;/span&gt;: 12 instances, all idiomatic. &lt;span class="caps"&gt;OEC&lt;/span&gt;: 484, and out of a sample of
12 all were&amp;nbsp;idiomatic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;When pigs fly&lt;/strong&gt;
    &lt;p&gt;
    We looked for the lemma &lt;em&gt;fly&lt;/em&gt; before the word &lt;em&gt;pigs&lt;/em&gt; therefore
    catching multiple variations.&lt;br /&gt;
    &lt;span class="caps"&gt;BNC&lt;/span&gt;: 17 hits, &lt;span class="caps"&gt;OEC&lt;/span&gt;:&amp;nbsp;240.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Judging a book by its&amp;nbsp;cover&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We looked for the fixed phrase &lt;em&gt;book by its cover&lt;/em&gt;, because the
leading verb might vary.&lt;br /&gt;
In the &lt;span class="caps"&gt;BNC&lt;/span&gt;, 11 instances (1 of them with tell instead of judge). In
&lt;span class="caps"&gt;OEC&lt;/span&gt;, 195 instances. Sampling 11, all were&amp;nbsp;idiomatic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Biting off more than one can&amp;nbsp;chew&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;BNC&lt;/span&gt;: 16 occurences, one of which with &amp;#8220;to take&amp;#8221; instead of &amp;#8220;to
bite&amp;#8221;. &lt;span class="caps"&gt;OEC&lt;/span&gt;: 231, all idiomatic after sampling&amp;nbsp;16.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scratching one&amp;#8217;s&amp;nbsp;back&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;BNC&lt;/span&gt;: 23, out of which only 5 idiomatic. &lt;span class="caps"&gt;OEC&lt;/span&gt;: 756, 5/23&amp;nbsp;idiomatic.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="recalculating-the-rank"&gt;Recalculating the rank&lt;a class="headerlink" href="#recalculating-the-rank" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;
We now have enough data to reorder the expressions and compare. The
result will be more approximate for the &lt;span class="caps"&gt;OEC&lt;/span&gt; because of our use of small
subsamples to estimate the frequencies, but hopefully it will still be
interesting. The way we are estimating the counts for the &lt;span class="caps"&gt;OEC&lt;/span&gt; is as
follows: take, for instance, *break a leg*. It was found 70 times, and
out of a sample of 16, 10 were literal. The expected number of idiomatic
uses is therefore:  

&lt;center&gt;
[latex]n = &amp;#92;left ( 1 - &amp;#92;frac{10}{16} &amp;#92;right ) &amp;#92;cdot 70 =
26.25[/latex]

&lt;/center&gt;

Repeating this computation and skipping a ton of steps leads to the
following&amp;nbsp;rankings:

&lt;/p&gt;
&lt;div style="float: left; margin-left: 5em;"&gt;
**In the British National&amp;nbsp;Corpus:**

&lt;/p&gt;
1.  Costing an arm and a leg
2.  Piece of cake
3.  When pigs fly
4.  Letting the cat out of the bag
5.  Biting off more than one can chew
6.  Hitting the nail on the head
7.  Judging a book by its cover
8.  Scratching one’s back
9.  Break a leg
10. Hitting the books

&lt;/div&gt;
&lt;div style="float: right; margin-right: 5em;"&gt;
**In the Oxford English&amp;nbsp;Corpus:**

&lt;/p&gt;
1.  Hitting the nail on the head
2.  Piece of cake
3.  Letting the cat out of the bag
4.  When pigs fly
5.  Biting off more than one can chew
6.  Costing an arm and a leg
7.  Judging a book by its cover
8.  Scratching one’s back
9.  Hitting the books
10. Break a leg

&lt;/div&gt;

&lt;p&gt;We can see that apart from the apparent switching of &lt;em&gt;hitting the nail
on the head&lt;/em&gt; with &lt;em&gt;costing an arm and a leg&lt;/em&gt;, the rankings are not too
different. We can quantify this by using the &lt;strong&gt;Rank Distance&lt;/strong&gt;, a metric
introduced by Liviu P. Dinu [&lt;a href="#f3"&gt;3&lt;/a&gt;, &lt;a href="#f4"&gt;4&lt;/a&gt;]. Here, all our 3 rankings are
over the same domain: we are not looking for the most frequent idioms in
the corpora, this would be very hard. We are just reordering the
proposed rank according to the occurrences in &lt;span class="caps"&gt;BNC&lt;/span&gt; and &lt;span class="caps"&gt;OEC&lt;/span&gt;. In this
simple case, Rank Distance reduces to [latex]\ell_1[/latex] distance
over rank position vectors. The weighted Rank Distance, bounded on
[latex][0, 1][/latex] is in this case given by a scaling factor of
[latex]0.5k\^2[/latex] where &lt;em&gt;k&lt;/em&gt; is the length of the rankings (10 in
our&amp;nbsp;case).&lt;/p&gt;
&lt;p&gt;The computed distance between the original ranking and the &lt;span class="caps"&gt;BNC&lt;/span&gt;
reordering is 0.52. Between the original and the &lt;span class="caps"&gt;OEC&lt;/span&gt; reordering, it is
0.68. Our two reorderings are much closer: the distance is 0.28. This is
mostly because that the permutations between the two reorderings affect
the top position, and are therefore weighted&amp;nbsp;more.&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s also interesting to look at the ratio of the counts. Interestingly,
they approximately differ by a constant factor not far from the relative
size difference of the two corpora, as would be&amp;nbsp;expected.&lt;/p&gt;
&lt;p&gt;We have to throw away &lt;em&gt;hitting the books&lt;/em&gt; because its &lt;span class="caps"&gt;BNC&lt;/span&gt; zero count
leads to divisions by zero. After this step, the average of the relative
counts of the idioms is 19.5, with a standard deviation of 10.1, while
&lt;span class="caps"&gt;OED&lt;/span&gt; is supposed to have around 20 times more words than the &lt;span class="caps"&gt;BNC&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="conclusions"&gt;Conclusions&lt;a class="headerlink" href="#conclusions" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Well, it seems people don&amp;#8217;t say &lt;em&gt;break a leg&lt;/em&gt; and &lt;em&gt;let&amp;#8217;s hit the books&lt;/em&gt;
as often as the original author claims. The popularity of most of the
cited idioms seems supported by the data, but we have no easy way to
find other idioms that might turn out to be much more frequent. Corpus
linguistics is a reliable way to measure the social salience of language
patterns It should always be used to verify and back empty claims of the
form &lt;em&gt;X is correct&lt;/em&gt;, &lt;em&gt;Y is frequent&lt;/em&gt; or &lt;em&gt;Nobody says Z&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;[&lt;span id="f1"&gt;1&lt;/span&gt;] Patrick Hanks, &lt;a href="http://www.patrickhanks.com/uploads/5/1/4/9/5149363/howpeopleusewordstomakemeanings.pdf"&gt;How people use words to make
meanings&lt;/a&gt;.&lt;br /&gt;
[&lt;span id="f2"&gt;2&lt;/span&gt;] Patrick Hanks, &lt;a href="http://www.amazon.com/Lexical-Analysis-Exploitations-Patrick-Hanks/dp/0262018578"&gt;Lexical Analysis: Norms and
Exploitations&lt;/a&gt;. The &lt;span class="caps"&gt;MIT&lt;/span&gt; Press (January 25, 2013)&lt;br /&gt;
[&lt;span id="f3"&gt;3&lt;/span&gt;] Liviu P. Dinu, Florin Manea. &lt;a href="http://dl.acm.org/citation.cfm?id=1167105"&gt;An efficient
approach for the rank aggregation problem&lt;/a&gt;. In: Theoretical Computer
Science, Volume 359 Issue 1, 14 August 2006. Pages 455 - 461.&lt;br /&gt;
[&lt;span id="f4"&gt;4&lt;/span&gt;] Liviu P. Dinu, [On the Classification and
Aggregation of Hierarchies with Different Constitutive Elements][].
Fundam. Inform. 55(1): 39-50&amp;nbsp;(2003)&lt;/p&gt;</content><category term="nlp"/><category term="bnc"/><category term="british national corpus"/><category term="corpus"/><category term="fixed expression"/><category term="fixed phrase"/><category term="idioms"/><category term="oec"/><category term="oxford english corpus"/><category term="corpus linguistics"/><category term="nlp"/></entry><entry><title>Scikit-learn-speed: An overview on the final day</title><link href="//vene.ro/blog/scikit-learn-speed-an-overview-on-the-final-day.html" rel="alternate"/><published>2012-08-20T02:44:00+02:00</published><updated>2012-08-20T02:44:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-08-20:/blog/scikit-learn-speed-an-overview-on-the-final-day.html</id><summary type="html">&lt;p&gt;This summer, I was granted the project called &lt;em&gt;scikit-learn-speed&lt;/em&gt;,
consisting of developing a benchmarking platform for &lt;em&gt;scikit-learn&lt;/em&gt; and
using it to find potential speedups, and in the end, make the library go
faster wherever I&amp;nbsp;can.&lt;/p&gt;
&lt;p&gt;On the official closing day of this work, I&amp;#8217;d like to take a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This summer, I was granted the project called &lt;em&gt;scikit-learn-speed&lt;/em&gt;,
consisting of developing a benchmarking platform for &lt;em&gt;scikit-learn&lt;/em&gt; and
using it to find potential speedups, and in the end, make the library go
faster wherever I&amp;nbsp;can.&lt;/p&gt;
&lt;p&gt;On the official closing day of this work, I&amp;#8217;d like to take a moment and
recall the accomplishments and failures of this project, and all the
lessons to be&amp;nbsp;learned.&lt;/p&gt;
&lt;h2 id="the-scikit-learn-speed-benchmark-platform"&gt;The &lt;em&gt;scikit-learn-speed&lt;/em&gt; benchmark platform&lt;a class="headerlink" href="#the-scikit-learn-speed-benchmark-platform" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2012/08/skl-speed-300x163.png" title="skl-speed" /&gt;]&lt;a href="http://localhost:8001/wp-content/uploads/2012/08/skl-speed-300x163.png" title="skl-speed"&gt;&lt;/a&gt;&lt;br /&gt;
[&lt;em&gt;Scikit-learn-speed&lt;/em&gt;][&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2012/08/skl-speed-300x163.png" title="skl-speed" /&gt;] is a continuous benchmark suite for the
&lt;a href="http://scikit-learn.org"&gt;&lt;em&gt;scikit-learn&lt;/em&gt;&lt;/a&gt; library. It has the following&amp;nbsp;features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;vbench&lt;/em&gt;-powered integration with&amp;nbsp;Git&lt;/li&gt;
&lt;li&gt;Easily triggered build and report generation: just&amp;nbsp;type &lt;code&gt;make&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Easily readable and writeable template for benchmarks:
    &lt;p&gt;
    [sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
    {&lt;br /&gt;
    &amp;#8216;obj&amp;#8217;: &amp;#8216;LogisticRegression&amp;#8217;,&lt;br /&gt;
    &amp;#8216;init_params&amp;#8217;: {&amp;#8216;C&amp;#8217;: 1e5},&lt;br /&gt;
    &amp;#8216;datasets&amp;#8217;: (&amp;#8216;arcene&amp;#8217;, &amp;#8216;madelon&amp;#8217;),&lt;br /&gt;
    &amp;#8216;statements&amp;#8217;: (&amp;#8216;fit&amp;#8217;, &amp;#8216;predict&amp;#8217;)&lt;br /&gt;
    }, &amp;#8230;&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/li&gt;
&lt;li&gt;Many attributes recorded: time (w/ estimated standard deviation),
    memory usage, cProfiler output, line_profiler output,&amp;nbsp;tracebacks&lt;/li&gt;
&lt;li&gt;Multi-step benchmarks:&amp;nbsp;i.e. &lt;code&gt;fit&lt;/code&gt; followed&amp;nbsp;by &lt;code&gt;predict&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What were the lessons I learned&amp;nbsp;here?&lt;/p&gt;
&lt;h3 id="make-your-work-reusable-the-trade-off-between-good-design-and-get-it-working-now"&gt;Make your work reusable: the trade-off between good design and get-it-working-now&lt;a class="headerlink" href="#make-your-work-reusable-the-trade-off-between-good-design-and-get-it-working-now" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For the task of rolling out a continuous benchmarking platform, we
decided pretty early in the project to adopt Wes McKinney&amp;#8217;s &lt;em&gt;vbench&lt;/em&gt;. If
my goal would&amp;#8217;ve been to maintain &lt;em&gt;vbench&lt;/em&gt; and extend it into a
multi-purpose, reusable benchmarking framework, the work would&amp;#8217;ve been
structured differently. It also would have been very open-ended and
difficult to&amp;nbsp;quantify.&lt;/p&gt;
&lt;p&gt;The way things have been, I came up with features that we need in
&lt;em&gt;scikit-learn-speed&lt;/em&gt;, and tried to implement them in &lt;em&gt;vbench&lt;/em&gt; without
refactoring too much, but still by trying to make them as reusable as&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;The result? I got all the features for &lt;em&gt;scikit-learn-speed&lt;/em&gt;, but the
implementation is not yet clean enough to be merged into &lt;em&gt;vbench&lt;/em&gt;. This
is fine for a project with a tight deadline such as this one: after it&amp;#8217;s
done, I will just spend another weekend on cleaning the work up and
making sure it&amp;#8217;s appreciated upstream. This will be easier because of
the constraint to keep compatibility with &lt;em&gt;scikit-learn-speed&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="never-work-quietly-unless-youre-a-ninja"&gt;Never work quietly (unless you&amp;#8217;re a ninja)&lt;a class="headerlink" href="#never-work-quietly-unless-youre-a-ninja" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I know some students who prefer that the professor doesn&amp;#8217;t even know
they exist until the final, when they would score an A, and (supposedly)
leave the professor amazed. In real life, plenty of people would be
interested in what you are doing, as long as they know about it. The &lt;span class="caps"&gt;PSF&lt;/span&gt;
goes a long way to help this, with the &amp;#8220;blog weekly&amp;#8221; rule. In the end,
however, it&amp;#8217;s all up to you to make sure that everybody who should know
finds out about your work. It will spare the world the duplicated work,
the abandoned projects, but most importantly, those people could point
you to things you have missed. Try to mingle in real-life as well,
attend conferences, meetups, coding&amp;nbsp;sprints.&lt;/p&gt;
&lt;p&gt;I was able to slightly &amp;#8220;join forces&amp;#8221; with a couple of people who
contacted me about my new &lt;em&gt;vbench&lt;/em&gt; features (Hi Jon and Joel!), I have
shaped my design slightly towards their requirements as well, and
hopefully the result will be a more general &lt;em&gt;vbench&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="the-speedups"&gt;The speedups&lt;a class="headerlink" href="#the-speedups" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once &lt;em&gt;scikit-learn-speed&lt;/em&gt; was up and running, I couldn&amp;#8217;t believe how
useful it is to be able to scroll, catch slow code and jump straight at
the profiler output with one click. I jumped on the following&amp;nbsp;speed-ups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple outputs in linear models. (&lt;a href="https://github.com/scikit-learn/scikit-learn/pull/913"&gt;&lt;span class="caps"&gt;PR&lt;/span&gt;&lt;/a&gt;)
    &lt;p&gt;
    Some of them proved trickier than expected, so I didn&amp;#8217;t implement it
    for all the module yet, but it is ready for some&amp;nbsp;estimators.&lt;/li&gt;
&lt;li&gt;Less callable functions passed around&amp;nbsp;in &lt;code&gt;FastICA&lt;/code&gt; (&lt;a href="https://github.com/scikit-learn/scikit-learn/pull/927"&gt;merged&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Speed&amp;nbsp;up &lt;code&gt;euclidean_distances&lt;/code&gt; by rewriting in Cython. (&lt;a href="https://github.com/scikit-learn/scikit-learn/pull/1006"&gt;&lt;span class="caps"&gt;PR&lt;/span&gt;&lt;/a&gt;)
    &lt;p&gt;
    This meant making more operations support&amp;nbsp;an &lt;code&gt;out&lt;/code&gt; argument, for
    passing preallocated memory. This touches many&lt;br /&gt;
    different objects in the codebase: clustering, manifold learning,
    nearest neighbour&amp;nbsp;methods.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://localhost:8001/2012/08/18/inverses-pseudoinverses-numerical-issues-speed-symmetry/" title="Inverses and pseudoinverses. Numerical issues, speed, symmetry."&gt;Insight into inverse and pseudoinverse computation&lt;/a&gt;,&amp;nbsp;new &lt;code&gt;pinvh&lt;/code&gt;
    function for inverting symmetric/hermitian matrices. (&lt;a href="https://github.com/scikit-learn/scikit-learn/pull/1015"&gt;&lt;span class="caps"&gt;PR&lt;/span&gt;&lt;/a&gt;)
    &lt;p&gt;
    This speeds up the covariance module&amp;nbsp;(especially &lt;code&gt;MinCovDet&lt;/code&gt;),
    &lt;code&gt;ARDRegression&lt;/code&gt; and the mixture models. It also lead to an &lt;a href="https://github.com/scipy/scipy/pull/289"&gt;upstream
    contribution to&amp;nbsp;Scipy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OrthogonalMatchingPursuit&lt;/code&gt; forward stepwise path for
    cross-validation (&lt;a href="https://github.com/scikit-learn/scikit-learn/pull/1042"&gt;&lt;span class="caps"&gt;PR&lt;/span&gt;&lt;/a&gt;)
    &lt;p&gt;
    This is only halfway finished, but it will lead to faster and easier
    optimization of&amp;nbsp;the &lt;code&gt;OMP&lt;/code&gt; sparsity&amp;nbsp;parameter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lessons? These will be pretty&amp;nbsp;obvious.&lt;/p&gt;
&lt;h3 id="write-tests-tests-tests"&gt;Write tests, tests, tests!&lt;a class="headerlink" href="#write-tests-tests-tests" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is a no-brainer, but it still didn&amp;#8217;t stick. In that one case out of
10 that I didn&amp;#8217;t explicitly test, a bug was obviously hiding. When you
want to add a new feature, it&amp;#8217;s best to start by writing a failing test,
and then &lt;a href="http://c2.com/cgi/wiki?MakeItWorkMakeItRightMakeItFast"&gt;making it pass&lt;/a&gt;. Sure, you will miss tricky bugs, but you
will never have embarrassing, obvious bugs in your code&amp;nbsp;:)&lt;/p&gt;
&lt;h3 id="optimization-doesnt-have-to-be-ugly"&gt;Optimization doesn&amp;#8217;t have to be ugly&lt;a class="headerlink" href="#optimization-doesnt-have-to-be-ugly" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Developers often shun optimization. It&amp;#8217;s true, you should profile first,
and you shouldn&amp;#8217;t focus on speeding up stuff that is dominated by other
computations that are orders of magnitude slower. However, there is an
elephant in the room: the assumption that making code faster invariably
makes it less clear, and takes a lot of&amp;nbsp;effort.&lt;/p&gt;
&lt;p&gt;The following code is a part of&amp;nbsp;scipy&amp;#8217;s &lt;code&gt;pinv2&lt;/code&gt; function as it currently
is written:&lt;br /&gt;
[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
cutoff = cond*np.maximum.reduce(s)&lt;br /&gt;
psigma = np.zeros((m, n), t)&lt;br /&gt;
for i in range(len(s)):&lt;br /&gt;
if s[i] &amp;gt; cutoff:&lt;br /&gt;
psigma[i,i] = 1.0/np.conjugate(s[i])&lt;br /&gt;
return np.transpose(np.conjugate(np.dot(np.dot(u,psigma),vh)))&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;&lt;code&gt;psigma&lt;/code&gt; is a diagonal matrix, and some time and memory can be saved
with simple vectorization. However, this part of the code dominated by
an above call&amp;nbsp;to &lt;code&gt;svd&lt;/code&gt;. The profiler output would say that we shouldn&amp;#8217;t
bother, but is it really a bother? Look at Jake&amp;#8217;s new&amp;nbsp;version:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
above_cutoff = (s &amp;gt; cond * np.max(s))&lt;br /&gt;
psigma_diag = np.zeros_like(s)&lt;br /&gt;
psigma_diag[above_cutoff] = 1.0 /&amp;nbsp;s[above_cutoff]&lt;/p&gt;
&lt;p&gt;return np.transpose(np.conjugate(np.dot(u * psigma_diag, vh)))&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s shorter, more elegant, easier to read, and nevertheless faster. I
would say it is worth&amp;nbsp;it.&lt;/p&gt;
&lt;h3 id="small-speed-ups-can-propagate"&gt;Small speed-ups can propagate&lt;a class="headerlink" href="#small-speed-ups-can-propagate" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Sure, it&amp;#8217;s great if you can compute an inverse two times faster, say in
0.5s instead of 1s. But if some algorithm calls this function in a loop
that might iterate 100, 300, or 1000 times, this small speed-up seems
much more important, doesn&amp;#8217;t&amp;nbsp;it?&lt;/p&gt;
&lt;p&gt;What I&amp;#8217;m trying to say with this is that in a well-engineered system, a
performance improvement to a relatively small component (such as the
function that computes a pseudoinverse) can lead to multiple spread out
improvements. Be careful of the double edge of this sword, a bug
introduced in a small part can cause multiple failures downstream. But
you &lt;em&gt;are&lt;/em&gt; fully covered by your test suite, aren&amp;#8217;t&amp;nbsp;you?&lt;/p&gt;
&lt;p&gt;Overall it has been a fruitful project that may have not resulted in a
large number of speed-ups, but a few considerable ones nonetheless. And
I venture the claim that the &lt;em&gt;scikit-learn-speed&lt;/em&gt; tool will prove useful
over time, and that the efforts deployed during this project have
stretched beyond the boundary of the &lt;em&gt;scikit-learn&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2012/08/skl-speed-300x163.png" title="skl-speed" /&gt;]:&amp;nbsp;http://jenkins-scikit-learn.github.com/scikit-learn-speed/&lt;/p&gt;</content><category term="scikit-learn"/><category term="gsoc"/><category term="optimization"/><category term="scikit-learn-speed"/><category term="speedup"/><category term="summary"/><category term="vbench"/><category term="benchmarking"/><category term="python"/><category term="scikit-learn"/></entry><entry><title>Inverses and pseudoinverses. Numerical issues, speed, symmetry.</title><link href="//vene.ro/blog/inverses-pseudoinverses-numerical-issues-speed-symmetry.html" rel="alternate"/><published>2012-08-18T19:41:00+02:00</published><updated>2012-08-18T19:41:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-08-18:/blog/inverses-pseudoinverses-numerical-issues-speed-symmetry.html</id><summary type="html">&lt;p&gt;The matrix inverse is a cornerstone of linear algebra, taught, along
with its applications, since high school. The inverse of a matrix
$latex A$, if it exists, is the matrix $latex A\^{-1}$ such that
$latex &lt;span class="caps"&gt;AA&lt;/span&gt;\^{-1} = A\^{-1}A = I_n$. Based on the requirement that the
left and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The matrix inverse is a cornerstone of linear algebra, taught, along
with its applications, since high school. The inverse of a matrix
$latex A$, if it exists, is the matrix $latex A\^{-1}$ such that
$latex &lt;span class="caps"&gt;AA&lt;/span&gt;\^{-1} = A\^{-1}A = I_n$. Based on the requirement that the
left and right multiplications should be equal, it follows that it only
makes sense to speak of inverting square matrices. But just the square
shape is not enough: for a matrix $latex A$ to have an inverse,
$latex A$ must be full&amp;nbsp;rank.&lt;/p&gt;
&lt;p&gt;The inverse provides an elegant (on paper) method of finding solutions
to systems of $latex n$ equations with $latex n$ unknowns, which
correspond to solving $latex Ax = b$ for $latex x$. If we&amp;#8217;re lucky
and $latex A\^{-1}$ exists, then we can find $latex x = A\^{-1}b$.
For this to work, it must be the case&amp;nbsp;that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We have exactly as many unknowns as&amp;nbsp;equations&lt;/li&gt;
&lt;li&gt;No equation is redundant, i.e. can be expressed as a linear
    combination of the&amp;nbsp;others&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this setting, there is a unique solution for $latex&amp;nbsp;x$.&lt;/p&gt;
&lt;h2 id="the-moore-penrose-pseudoinverse"&gt;The Moore-Penrose pseudoinverse&lt;a class="headerlink" href="#the-moore-penrose-pseudoinverse" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What if we have more equations than unknowns? It is most likely the case
that we cannot satisfy all the equations perfectly, so let&amp;#8217;s settle for
a solution that best fits the constraints, in the sense of minimising
the sum of squared errors. We solve $latex \operatorname{arg\,min}_x
||b -&amp;nbsp;Ax||$.&lt;/p&gt;
&lt;p&gt;And how about the other extreme, where we have a lot of unknowns, but
just a few equations constraining them. We will probably have an
infinity of solutions, how can we choose one? A popular choice is to
take the one of least $latex \ell_2$ norm: $latex
\operatorname{arg\,min}_x ||x|| \operatorname{s.t.} Ax = b$. Is
there a way to generalize the idea of a matrix inverse for this&amp;nbsp;setting?&lt;/p&gt;
&lt;p&gt;The pseudoinverse of an arbitrary-shaped matrix $latex A$, written
$latex A\^{+}$, has the same shape as $latex A\^{T}$ and solves our
problem: the answer to both optimization methods above is given by
$latex x =&amp;nbsp;A\^{+}y$.&lt;/p&gt;
&lt;p&gt;The theoretical definition of the pseudoinverse is given by the
following conditions. The intuitive way to read them is as properties of
$latex &lt;span class="caps"&gt;AA&lt;/span&gt;\^+$ or $latex&amp;nbsp;A\^+A$:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$latex &lt;span class="caps"&gt;AA&lt;/span&gt;\^+A =&amp;nbsp;A$&lt;/li&gt;
&lt;li&gt;$latex A\^+&lt;span class="caps"&gt;AA&lt;/span&gt;\^+ =&amp;nbsp;A\^+$&lt;/li&gt;
&lt;li&gt;$latex (&lt;span class="caps"&gt;AA&lt;/span&gt;\^+)\^T = &lt;span class="caps"&gt;AA&lt;/span&gt;\^+$&lt;/li&gt;
&lt;li&gt;$latex (A\^+A)\^T =&amp;nbsp;A\^+A$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These conditions do not however give us a way to get our hands on a
pseudoinverse, so we need something&amp;nbsp;else.&lt;/p&gt;
&lt;h2 id="how-to-compute-the-pseudoinverse-on-paper"&gt;How to compute the pseudoinverse on paper&lt;a class="headerlink" href="#how-to-compute-the-pseudoinverse-on-paper" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first time I ran into the pseudoinverse, I didn&amp;#8217;t even know its
definition, only the expression of the closed-form solution of such a
problem, and given&amp;nbsp;as:&lt;/p&gt;
&lt;p&gt;$latex A\^+ = (A\^T&amp;nbsp;A)\^{-1}A\^T$&lt;/p&gt;
&lt;p&gt;What can we see from this&amp;nbsp;expression:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It gives us a way to compute the pseudoinverse, and hence to solve
    the&amp;nbsp;problem&lt;/li&gt;
&lt;li&gt;If $latex A$ is actually invertible, it means $latex A\^T$ is
    invertible, so we have $latex A\^+ = A\^{-1}(A\^T)\^{-1}A\^T =&amp;nbsp;A\^{-1}$&lt;/li&gt;
&lt;li&gt;Something bad happens if $latex A\^&lt;span class="caps"&gt;TA&lt;/span&gt;$ is not&amp;nbsp;invertible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pseudoinverse is still defined, and unique, when $latex A\^&lt;span class="caps"&gt;TA&lt;/span&gt;$ is
not invertible, but we cannot use the expression above to compute&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="numerical-issues"&gt;Numerical issues&lt;a class="headerlink" href="#numerical-issues" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before going on, we should clarify and demystify some of the urban
legends about numerical computation of least squares problems. You might
have heard the following unwritten&amp;nbsp;rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Never compute $latex A\^{-1}$, solve the system&amp;nbsp;directly&lt;/li&gt;
&lt;li&gt;If you really need $latex A\^{-1}$,&amp;nbsp;use &lt;code&gt;pinv&lt;/code&gt; and&amp;nbsp;not &lt;code&gt;inv&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first of these rules is based on some misguided beliefs, but is
still good advice. If your goal is a one-shot answer to a system,
there&amp;#8217;s no use in explicitly computing a possibly large inverse, when
all you need is $latex x$. But &lt;a href="http://arxiv.org/abs/1201.6035"&gt;this paper&lt;/a&gt; shows that computing the
inverse is not necessarily a bad thing. The key to this is conditional
accuracy, and as long as&amp;nbsp;the &lt;code&gt;inv&lt;/code&gt; function used has good conditional
bounds, you will get as good results as with a least squares&amp;nbsp;solver.&lt;/p&gt;
&lt;p&gt;The second rule comes from numerical stability, and will definitely bite
you if misunderstood. If $latex A$ is a square matrix with a row full
of zeros, it&amp;#8217;s clearly not invertible, so an algorithm attempting to
compute the inverse will fail and you will be able to catch that
failure. But what if the row is not exactly zero, but the sum of several
other rows, and a slight loss of precision is propagated at every&amp;nbsp;step?&lt;/p&gt;
&lt;h2 id="numerical-rank-vs-actual-rank"&gt;Numerical rank vs. actual rank&lt;a class="headerlink" href="#numerical-rank-vs-actual-rank" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The rank of a matrix $latex A$ is defined as the number of linearly
independent rows (or equivalently, columns) in $latex A$. In other
words, the number of non-redundant equations in the system. We&amp;#8217;ve seen
before that if the rank is less than the total number of rows, the
system cannot have a unique solution anymore, so the matrix $latex A$
is not&amp;nbsp;invertible.&lt;/p&gt;
&lt;p&gt;The rank of a matrix is a computationally tricky problem. On paper, with
small matrices, you would look at minors of decreasing size, until you
find the first non-zero one. This is unfeasible to implement on a
computer, so numerical analysis has a different approach. Enter the
singular value&amp;nbsp;decomposition!&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;SVD&lt;/span&gt; of a matrix $latex A$ is $latex A = &lt;span class="caps"&gt;USV&lt;/span&gt;\^{T}$, where $latex
S$ is diagonal and $latex U, V$ are orthogonal. The elements on the
diagonal of $latex S$ are called the singular values of $latex A$.
It can be seen that to get a row full of zeros when multiplying three
such matrices, a singular value needs to be exactly&amp;nbsp;zero.&lt;/p&gt;
&lt;p&gt;The ugly thing that could happen is that one (or usually more) singular
values are not exactly zero, but very low values, due to propagated
imprecision. Why is this a problem? By looking at the &lt;span class="caps"&gt;SVD&lt;/span&gt; and noting its
properties, it becomes clear that $latex A\^{-1} = &lt;span class="caps"&gt;VS&lt;/span&gt;\^{-1}U\^{T}$ and
since $latex S$ is diagonal, its inverse is formed by taking the
inverse of all the elements on the diagonal. But if a singular value is
very small but not quite zero, its inverse is very large and it will
blow up the whole computation of the inverse. The right thing to do here
is either to tell the user that $latex A$ is numerically rank
deficient, or to return a pseudoinverse instead. A pseudoinverse would
mean: give up on trying to get $latex &lt;span class="caps"&gt;AA&lt;/span&gt;\^+$ to be the identity
matrix, simply aim for a diagonal matrix with approximately ones and
zeroes. In other words, when singular values are very low, set them to&amp;nbsp;0.&lt;/p&gt;
&lt;p&gt;How do you set the threshold? This is actually a delicate issue, being
discussed on &lt;a href="http://thread.gmane.org/gmane.comp.python.numeric.general/50396/focus=50912"&gt;the numeric Python mailing list&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="scipy-implementations"&gt;Scipy implementations&lt;a class="headerlink" href="#scipy-implementations" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Scipy&amp;nbsp;exposes &lt;code&gt;inv&lt;/code&gt;, &lt;code&gt;pinv&lt;/code&gt; and &lt;code&gt;pinv2&lt;/code&gt;. &lt;code&gt;inv&lt;/code&gt; secretly invokes &lt;span class="caps"&gt;LAPACK&lt;/span&gt;,
that ancient but crazy robust code that&amp;#8217;s been used since the 70s, to
first compute a pivoted &lt;span class="caps"&gt;LU&lt;/span&gt; decomposition that is then used to compute
the&amp;nbsp;inverse. &lt;code&gt;pinv&lt;/code&gt; also uses &lt;span class="caps"&gt;LAPACK&lt;/span&gt;, but for computing the
least-squares solution to the system $latex &lt;span class="caps"&gt;AX&lt;/span&gt; =&amp;nbsp;I$. &lt;code&gt;pinv2&lt;/code&gt; computes
the &lt;span class="caps"&gt;SVD&lt;/span&gt; and transposes everything like shown above.&amp;nbsp;Both &lt;code&gt;pinv&lt;/code&gt; and
&lt;code&gt;pinv2&lt;/code&gt; expose &lt;code&gt;cond&lt;/code&gt; and &lt;code&gt;rcond&lt;/code&gt; arguments to handle the treatment of
very small singular values, but (&lt;em&gt;attention!&lt;/em&gt;) they behave&amp;nbsp;differently!&lt;/p&gt;
&lt;p&gt;The different implementations also lead to different speed. Let&amp;#8217;s look
at inverting a random square&amp;nbsp;matrix:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [1]: import numpy as&amp;nbsp;np&lt;/p&gt;
&lt;p&gt;In [2]: from scipy import&amp;nbsp;linalg&lt;/p&gt;
&lt;p&gt;In [3]: a = np.random.randn(1000,&amp;nbsp;1000)&lt;/p&gt;
&lt;p&gt;In [4]: timeit linalg.inv(a)&lt;br /&gt;
10 loops, best of 3: 132 ms per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [5]: timeit linalg.pinv(a)&lt;br /&gt;
1 loops, best of 3: 18.8 s per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [6]: timeit linalg.pinv2(a)&lt;br /&gt;
1 loops, best of 3: 1.58 s per loop&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Woah, huge difference! But do all three methods return the &amp;#8220;right&amp;#8221;&amp;nbsp;result?&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [7]: linalg.inv(a)[:3, :3]&lt;br /&gt;
Out[7]:&lt;br /&gt;
array([[ 0.03636918, 0.01641725, 0.00736503],&lt;br /&gt;
[-0.04575771, 0.03578062, 0.02937733],&lt;br /&gt;
[ 0.00542367, 0.01246306, 0.0122156&amp;nbsp;]])&lt;/p&gt;
&lt;p&gt;In [8]: linalg.pinv(a)[:3, :3]&lt;br /&gt;
Out[8]:&lt;br /&gt;
array([[ 0.03636918, 0.01641725, 0.00736503],&lt;br /&gt;
[-0.04575771, 0.03578062, 0.02937733],&lt;br /&gt;
[ 0.00542367, 0.01246306, 0.0122156&amp;nbsp;]])&lt;/p&gt;
&lt;p&gt;In [9]: linalg.pinv2(a)[:3, :3]&lt;br /&gt;
Out[9]:&lt;br /&gt;
array([[ 0.03636918, 0.01641725, 0.00736503],&lt;br /&gt;
[-0.04575771, 0.03578062, 0.02937733],&lt;br /&gt;
[ 0.00542367, 0.01246306, 0.0122156&amp;nbsp;]])&lt;/p&gt;
&lt;p&gt;In [10]: np.testing.assert_array_almost_equal(linalg.inv(a),&amp;nbsp;linalg.pinv(a))&lt;/p&gt;
&lt;p&gt;In [11]: np.testing.assert_array_almost_equal(linalg.inv(a),
linalg.pinv2(a))&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Looks good! This is because we got lucky, though,&amp;nbsp;and &lt;code&gt;a&lt;/code&gt; was invertible
to start with. Let&amp;#8217;s look at its&amp;nbsp;spectrum:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [12]: _, s, _ =&amp;nbsp;linalg.svd(a)&lt;/p&gt;
&lt;p&gt;In [13]: np.min(s), np.max(s)&lt;br /&gt;
Out[13]: (0.029850235603382822, 62.949785645178906)&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;This is a lovely range for the singular values of a matrix, not too
small, not too large. But what if we built the matrix in a way that
would always pose problems? Specifically, let&amp;#8217;s look at the case of
covariance&amp;nbsp;matrices:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [14]: a = np.random.randn(1000,&amp;nbsp;50)&lt;/p&gt;
&lt;p&gt;In [15]: a = np.dot(a,&amp;nbsp;a.T)&lt;/p&gt;
&lt;p&gt;In [16]: _, s, _ =&amp;nbsp;linalg.svd(a)&lt;/p&gt;
&lt;p&gt;In [17]: s[-9:]&lt;br /&gt;
Out[17]:&lt;br /&gt;
array([ 7.40548924e-14, 6.48102455e-14, 5.75803505e-14,&lt;br /&gt;
5.44263048e-14, 4.51528730e-14, 3.55317976e-14,&lt;br /&gt;
2.46939141e-14, 1.54186776e-14,&amp;nbsp;5.08135874e-15])&lt;/p&gt;
&lt;p&gt;[/sourcecode]&lt;/p&gt;
&lt;p&gt;&lt;code&gt;a&lt;/code&gt; has at least 9 tiny singular values. Actually it&amp;#8217;s easy to see why
there are 950 of&amp;nbsp;them:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [18]: np.sum(s \&amp;lt; 1e-10)&lt;br /&gt;
Out[18]: 950&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;How do our functions behave in this case? Instead of just looking at a
corner, let&amp;#8217;s use our gift of sight:[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2012/08/pseudoinverses-300x218.png" title="Pseudoinverses" /&gt;]&lt;a href="http://localhost:8001/wp-content/uploads/2012/08/pseudoinverses-300x218.png" title="Pseudoinverses"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The small eigenvalues are large enough&amp;nbsp;that &lt;code&gt;inv&lt;/code&gt; thinks the matrix is
full&amp;nbsp;rank. &lt;code&gt;pinv&lt;/code&gt; does better but it still fails, you can see a group of
high-amplitude noisy&amp;nbsp;columns. &lt;code&gt;pinv2&lt;/code&gt; is faster and it also gives us a
useful result in this&amp;nbsp;case.&lt;/p&gt;
&lt;p&gt;Wait, does this mean&amp;nbsp;that &lt;code&gt;pinv2&lt;/code&gt; is simply better,&amp;nbsp;and &lt;code&gt;pinv&lt;/code&gt; is&amp;nbsp;useless?&lt;/p&gt;
&lt;p&gt;Not quite. Remember, we are now trying to actually invert matrices, and
degrade gracefully in case of rank deficiency. But what if we need the
pseudoinverse to solve an actual non-square, wide or tall&amp;nbsp;system?&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [19]: a = np.random.randn(1000,&amp;nbsp;50)&lt;/p&gt;
&lt;p&gt;In [20]: timeit linalg.pinv(a)&lt;br /&gt;
10 loops, best of 3: 104 ms per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [21]: timeit linalg.pinv(a.T)&lt;br /&gt;
100 loops, best of 3: 7.08 ms per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [22]: timeit linalg.pinv2(a)&lt;br /&gt;
10 loops, best of 3: 114 ms per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [23]: timeit linalg.pinv2(a.T)&lt;br /&gt;
10 loops, best of 3: 126 ms per loop&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Huge victory&amp;nbsp;for &lt;code&gt;pinv&lt;/code&gt; in the wide case! Hurray! With all this insight,
we can draw a line and see what we&amp;nbsp;learned.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you are 100% sure that your matrix is invertible,&amp;nbsp;use &lt;code&gt;inv&lt;/code&gt; for a
    huge speed gain. The implementation&amp;nbsp;of &lt;code&gt;inv&lt;/code&gt; from Scipy is based on
    &lt;span class="caps"&gt;LAPACK&lt;/span&gt;&amp;#8217;s &lt;code&gt;*getrf&lt;/code&gt; + &lt;code&gt;*getri&lt;/code&gt;, known to have good&amp;nbsp;bounds.&lt;/li&gt;
&lt;li&gt;If you are trying to solve a tall or wide system,&amp;nbsp;use &lt;code&gt;pinv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If your matrix is square but might be rank deficient,&amp;nbsp;use &lt;code&gt;pinv2&lt;/code&gt;
    for speed and numerical&amp;nbsp;gain.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="improving-the-symmetric-case"&gt;Improving the symmetric case&lt;a class="headerlink" href="#improving-the-symmetric-case" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;But wait a second, can&amp;#8217;t we do better? $latex &lt;span class="caps"&gt;AA&lt;/span&gt;\^T$ is symmetric,
can&amp;#8217;t we make use of that to speed up the computation even more?
Clearly, if $latex A$ is symmetric, in its &lt;span class="caps"&gt;SVD&lt;/span&gt; $latex A = &lt;span class="caps"&gt;USV&lt;/span&gt;\^T$,
we must have $latex U = V$. But this is exactly the eigendecomposition
of a symmetric matrix $latex A$. The eigendecomposition can be
computed cheaper than the &lt;span class="caps"&gt;SVD&lt;/span&gt; using&amp;nbsp;Scipy &lt;code&gt;eigh&lt;/code&gt;, that uses &lt;span class="caps"&gt;LAPACK&lt;/span&gt;&amp;#8217;s
&lt;code&gt;*evr&lt;/code&gt;. As part of my GSoC this year, with help from &lt;a href="http://jakevdp.github.com/"&gt;Jake
VanderPlas&lt;/a&gt;, we made a &lt;a href="https://github.com/scipy/scipy/pull/289"&gt;pull request to Scipy&lt;/a&gt; containing&amp;nbsp;a &lt;code&gt;pinvh&lt;/code&gt;
function that is equivalent&amp;nbsp;to &lt;code&gt;pinv2&lt;/code&gt; but faster for symmetric&amp;nbsp;matrices.&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [24]: timeit linalg.pinv2(a)&lt;br /&gt;
1 loops, best of 3: 1.54 s per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [25]: timeit linalg.pinvh(a)&lt;br /&gt;
1 loops, best of 3: 621 ms per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [26]: np.testing.assert_array_almost_equal(linalg.pinv2(a),
linalg.pinvh(a))&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2012/08/pseudoinverses-300x218.png" title="Pseudoinverses" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2012/08/pseudoinverses.png&lt;/p&gt;</content><category term="benchmarking"/><category term="inv"/><category term="matrix inverse"/><category term="numerical analysis"/><category term="numerical methods"/><category term="pinv"/><category term="pinvh"/><category term="positive semidefinite"/><category term="pseudoinverse"/><category term="symmetric"/><category term="benchmarking"/><category term="python"/></entry><entry><title>The scikit-learn-speed ship has set sail! Faster than ever, with multi-step benchmarks!</title><link href="//vene.ro/blog/the-scikit-learn-speed-ship-has-set-sail-faster-than-ever-with-multi-step-benchmarks.html" rel="alternate"/><published>2012-08-11T17:32:00+02:00</published><updated>2012-08-11T17:32:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-08-11:/blog/the-scikit-learn-speed-ship-has-set-sail-faster-than-ever-with-multi-step-benchmarks.html</id><summary type="html">&lt;p&gt;I am pleased to announce that last night at 2:03 &lt;span class="caps"&gt;AM&lt;/span&gt;, the first fully
automated run of the scikit-learn-speed test suite has run on our
Jenkins instance! You can admire it at &lt;a href="http://jenkins-scikit-learn.github.com/scikit-learn-speed/"&gt;its temporary home&lt;/a&gt; for now.
As soon as we verify that everything is good, we will move …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am pleased to announce that last night at 2:03 &lt;span class="caps"&gt;AM&lt;/span&gt;, the first fully
automated run of the scikit-learn-speed test suite has run on our
Jenkins instance! You can admire it at &lt;a href="http://jenkins-scikit-learn.github.com/scikit-learn-speed/"&gt;its temporary home&lt;/a&gt; for now.
As soon as we verify that everything is good, we will move this to the
official scikit-learn&amp;nbsp;page.&lt;/p&gt;
&lt;p&gt;I would like to take this opportunity to tell you about our latest
changeset. We made running the benchmark suite tons simpler by adding a
friendly Makefile. You can read more about its usage in the guide. But
by far, our coolest new toy&amp;nbsp;is:&lt;/p&gt;
&lt;h2 id="multi-step-benchmarks"&gt;Multi-step benchmarks&lt;a class="headerlink" href="#multi-step-benchmarks" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A standard vbench benchmark has three units of code, represented as&amp;nbsp;strings: &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;cleanup&lt;/code&gt;. With the original timeit-based
benchmarks, this means that for every run, the setup would be executed
once. Then, the main loop&amp;nbsp;runs &lt;code&gt;repeat&lt;/code&gt; times, and within each
iteration,&amp;nbsp;the &lt;code&gt;code&lt;/code&gt; is&amp;nbsp;run &lt;code&gt;ncalls&lt;/code&gt; times.&amp;nbsp;Then &lt;code&gt;cleanup&lt;/code&gt; happens, the
best time is returned, and everybody is&amp;nbsp;happy.&lt;/p&gt;
&lt;p&gt;In scikit-learn, most of our interesting objects go through a state
change called &lt;em&gt;fitting&lt;/em&gt;. This metaphor is right at home in the machine
learning field, where we separate the learning phase for the prediction
phase. The prediction step cannot be invoked on an object that hasn&amp;#8217;t
been&amp;nbsp;fitted.&lt;/p&gt;
&lt;p&gt;For some algorithms, one of these steps is trivial. A brute force
Nearest Neighbors classifier can be instantaneously fit, but prediction
takes a while. On the opposite end we have linear models, with tons of
complicated algorithms to fit them, but evaluation is a simple
matrix-vector product that Numpy handles&amp;nbsp;perfectly.&lt;/p&gt;
&lt;p&gt;But many of scikit-learn&amp;#8217;s estimators have both steps interesting. Let&amp;#8217;s
take Non-negative Matrix Factorization. It has three interesting
functions:&amp;nbsp;The &lt;code&gt;fit&lt;/code&gt; that computes $latex X = &lt;span class="caps"&gt;WH&lt;/span&gt; $,&amp;nbsp;the &lt;code&gt;transform&lt;/code&gt;
that computes a non-negative projection on the components learned&amp;nbsp;in
&lt;code&gt;fit&lt;/code&gt;,&amp;nbsp;and &lt;code&gt;fit_transform&lt;/code&gt; that takes advantage of the observation that
when fitting, we also get the transformed $latex X $ for&amp;nbsp;free.&lt;/p&gt;
&lt;p&gt;When benchmarking &lt;span class="caps"&gt;NMF&lt;/span&gt;, we initially had to design 3&amp;nbsp;benchmarks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;setup =&lt;/code&gt;standard, &lt;code&gt;code = obj.fit(X)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setup =&lt;/code&gt;standard, &lt;code&gt;code = obj.fit_transform(X)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setup =&lt;/code&gt;standard&lt;code&gt;+ obj.fit(X)&lt;/code&gt;, &lt;code&gt;code = obj.transform(X)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-much-time-were-we-wasting"&gt;How much time were we wasting?&lt;a class="headerlink" href="#how-much-time-were-we-wasting" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s say it takes 10 seconds. For every benchmark, we time the code by
running it 3 times. We run it once more to measure memory usage, once
more&amp;nbsp;for &lt;code&gt;cProfile&lt;/code&gt; and one last time&amp;nbsp;for &lt;code&gt;line_profiler&lt;/code&gt;. This is a
total of 6 times per benchmark. We need to multiply this by 2 again for
running on two datasets. So when&amp;nbsp;benchmarking &lt;code&gt;NMF&lt;/code&gt;, because we need to
fit before predicting, we do it 12 extra times. If a fit takes 5
seconds, this means one minute wasted on benchmarking just one
estimator. &lt;em&gt;Wouldn&amp;#8217;t it be nice&amp;nbsp;to &lt;code&gt;fit&lt;/code&gt;, &lt;code&gt;fit_transform&lt;/code&gt; and
&lt;code&gt;transform&lt;/code&gt; in a&amp;nbsp;sequence?&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="behind-the-scenes"&gt;Behind the scenes&lt;a class="headerlink" href="#behind-the-scenes" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We made&amp;nbsp;the &lt;code&gt;PythonBenchmark code&lt;/code&gt; parameter also support getting a
sequence of strings, instead of just a string. On the database side,
every benchmark result entry gets an extra component in the primary key,
the number of the step it&amp;nbsp;measures.&lt;/p&gt;
&lt;p&gt;In the benchmark description files, nothing is&amp;nbsp;changed:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
{&lt;br /&gt;
&amp;#8216;obj&amp;#8217;: &amp;#8216;&lt;span class="caps"&gt;NMF&lt;/span&gt;&amp;#8217;,&lt;br /&gt;
&amp;#8216;init_params&amp;#8217;: {&amp;#8216;n_components&amp;#8217;: 2},&lt;br /&gt;
&amp;#8216;datasets&amp;#8217;: (&amp;#8216;blobs&amp;#8217;,),&lt;br /&gt;
&amp;#8216;statements&amp;#8217;: (&amp;#8216;fit_unsup&amp;#8217;, &amp;#8216;transform_unsup&amp;#8217;, &amp;#8216;fit_transform&amp;#8217;)&lt;br /&gt;
},&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;But before, we would take the cartesian product of datasets and
statements, and build&amp;nbsp;a &lt;code&gt;Benchmark&lt;/code&gt; object for every pairing. Now, we
just pass the tuple as it is, and vbench is smart enough to do the right
thing.&lt;br /&gt;
We avoided the extra calls&amp;nbsp;to &lt;code&gt;fit&lt;/code&gt; in a lot of benchmarks. The whole
suite now takes almost half the time to&amp;nbsp;run!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; This trick is currently hosted in&amp;nbsp;the
&lt;code&gt;abstract_multistep_benchmarks&lt;/code&gt; vbench branch in my&amp;nbsp;fork.&lt;/p&gt;</content><category term="scikit-learn"/><category term="multi-step"/><category term="multistep"/><category term="vbench"/><category term="benchmarking"/><category term="python"/><category term="scikit-learn"/></entry><entry><title>Profiler output, benchmark standard deviation and other goodies in scikit-learn-speed</title><link href="//vene.ro/blog/profiler-output-benchmark-standard-deviation-and-other-goodies-in-scikit-learn-speed.html" rel="alternate"/><published>2012-07-27T11:01:00+02:00</published><updated>2012-07-27T11:01:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-07-27:/blog/profiler-output-benchmark-standard-deviation-and-other-goodies-in-scikit-learn-speed.html</id><summary type="html">&lt;p&gt;This post is about the &lt;a href="http://scikit-learn.org"&gt;scikit-learn&lt;/a&gt;benchmarking project that I am
working on, called &lt;a href="https://github.com/vene/scikit-learn-speed"&gt;scikit-learn-speed&lt;/a&gt;. This is a continuous
benchmarking suite that runs and generates &lt;span class="caps"&gt;HTML&lt;/span&gt; reports using Wes
McKinney&amp;#8217;s &lt;a href="http://wesmckinney.com/blog/?p=373"&gt;vbench&lt;/a&gt; framework, to which I had to make some (useful, I
hope)&amp;nbsp;additions.&lt;/p&gt;
&lt;h2 id="what-it-looks-like-now"&gt;What it looks like now&lt;a class="headerlink" href="#what-it-looks-like-now" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This post is about the &lt;a href="http://scikit-learn.org"&gt;scikit-learn&lt;/a&gt;benchmarking project that I am
working on, called &lt;a href="https://github.com/vene/scikit-learn-speed"&gt;scikit-learn-speed&lt;/a&gt;. This is a continuous
benchmarking suite that runs and generates &lt;span class="caps"&gt;HTML&lt;/span&gt; reports using Wes
McKinney&amp;#8217;s &lt;a href="http://wesmckinney.com/blog/?p=373"&gt;vbench&lt;/a&gt; framework, to which I had to make some (useful, I
hope)&amp;nbsp;additions.&lt;/p&gt;
&lt;h2 id="what-it-looks-like-now"&gt;What it looks like now&lt;a class="headerlink" href="#what-it-looks-like-now" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can check out a &lt;a href="http://vene.github.com/scikit-learn-speed"&gt;teaser/demo&lt;/a&gt; that was run on equidistant releases
from the last two months. What has changed since the last version?
Here&amp;#8217;s a list in order of&amp;nbsp;obviousness:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We now use the lovely scikit-learn&amp;nbsp;theme&lt;/li&gt;
&lt;li&gt;Timing graphs now show the ±1 standard deviation&amp;nbsp;range&lt;/li&gt;
&lt;li&gt;cProfile output is displayed for all the benchmarks, so we can
    easily see at a glance what&amp;#8217;s&amp;nbsp;up&lt;/li&gt;
&lt;li&gt;Said profiler output is collapsible using &lt;a href="http://www.jqueryui.com/demos/accordion/"&gt;JQueryUI&amp;nbsp;goodness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;There now is an improved &lt;a href="http://vene.github.com/scikit-learn-speed/quick_start.html"&gt;Quick Start guide&lt;/a&gt; to running vbench on
    your&amp;nbsp;machine&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-made-this-possible"&gt;What made this possible&lt;a class="headerlink" href="#what-made-this-possible" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have done some more refactoring in my vbench fork, because I didn&amp;#8217;t
want to have a huge,&amp;nbsp;monolithic &lt;code&gt;Benchmark&lt;/code&gt; class that was specific to
what we want in scikit-learn-speed. So on this branch, I set up a
mixin/multiple inheritance hierarchy of benchmark&amp;nbsp;classes.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Benchmark&lt;/code&gt; class in vbench is now an abstract base class, with some
common functionality and structure.&lt;br /&gt;&amp;nbsp;Our &lt;code&gt;SklBenchmark&lt;/code&gt; class is defined in scikit-learn-speed&amp;nbsp;as:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;class SklBenchmark(CProfileBenchmarkMixin,  MemoryBenchmarkMixin, PythonBenchmark):&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s read this from right to&amp;nbsp;left:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PythonBenchmark&lt;/code&gt;: This class&amp;nbsp;stores &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;cleanup&lt;/code&gt;
    Python code as strings, and implements simple timing mechanisms
    using&amp;nbsp;the &lt;code&gt;time&lt;/code&gt; module.&lt;/li&gt;
&lt;li&gt;Bonus: &lt;code&gt;TimeitBenchmark&lt;/code&gt;: This class&amp;nbsp;extends &lt;code&gt;PythonBenchmark&lt;/code&gt; with&amp;nbsp;the &lt;code&gt;timeit&lt;/code&gt; micro-benchmark timing method previously used in
    vbench. We turned this off in&amp;nbsp;scikit-learn-speed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MemoryBenchmarkMixin&lt;/code&gt;: This adds memory benchmarking using&amp;nbsp;[memory_profiler][].&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CProfileBenchmarkMixin&lt;/code&gt;: This runs the code through &lt;a href="http://docs.python.org/library/profile.html#module-cProfile"&gt;cProfile&lt;/a&gt;
    and implements mechanisms to report the output.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The database is not flexible enough to adapt to arbitrary benchmark
structure right now, so if anybody would like to help the effort, it
would be very&amp;nbsp;appreciated.&lt;/p&gt;</content><category term="scikit-learn"/><category term="gsoc"/><category term="memory_profiler"/><category term="scikit-learn-speed"/><category term="vbench"/><category term="benchmarking"/><category term="python"/><category term="scikit-learn"/></entry><entry><title>Scikit-learn-speed HTML reports teaser</title><link href="//vene.ro/blog/scikit-learn-speed-html-reports-teaser.html" rel="alternate"/><published>2012-07-20T14:40:00+02:00</published><updated>2012-07-20T14:40:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-07-20:/blog/scikit-learn-speed-html-reports-teaser.html</id><summary type="html">&lt;p&gt;&lt;span class="caps"&gt;EDIT&lt;/span&gt;: I made the plots a little more readable, check it&amp;nbsp;out!&lt;/p&gt;
&lt;p&gt;Last time, I teased you with a screenshot of local output. Now, I will
tease you with the benchmarks run on a couple of recent commits, along
with some from earlier this&amp;nbsp;year.&lt;/p&gt;
&lt;p&gt;After some effort and bugfixes …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;span class="caps"&gt;EDIT&lt;/span&gt;: I made the plots a little more readable, check it&amp;nbsp;out!&lt;/p&gt;
&lt;p&gt;Last time, I teased you with a screenshot of local output. Now, I will
tease you with the benchmarks run on a couple of recent commits, along
with some from earlier this&amp;nbsp;year.&lt;/p&gt;
&lt;p&gt;After some effort and bugfixes, the project now reliably runs on
different machines, so the next step to host it on a remote server and
invoke it daily is getting closer. In the mean time, you can have a look
at &lt;a href="http://vene.github.com/scikit-learn-speed/" title="scikit-learn-speed"&gt;the sample output&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that just last time, the plots look jagged but the differences are
mostly minor and significant conclusions cannot be drawn yet, but as the
suite will start running daily, the plots will become much more
meaningful. I could waste time running the suite on more previous
commits, but the results wouldn&amp;#8217;t be comparable with the ones from the
deployed system, because of hardware&amp;nbsp;differences.&lt;/p&gt;
&lt;p&gt;Playing around with this makes me want a couple of features in vbench.
One is the possibility to overlay related benchmarks on the same plot
(for example, different parameters for the same algorithm and data):
this could be useful to spot patterns. A second one is some query /
sorting support: see what are the most expensive benchmarks, see what
benchmarks show the biggest jump in performance (but this could become a
historical wall of fame or&amp;nbsp;shame).&lt;/p&gt;</content><category term="scikit-learn"/><category term="gsoc"/><category term="scikit-learn-speed"/><category term="vbench"/><category term="benchmarking"/><category term="python"/><category term="scikit-learn"/></entry><entry><title>Memory benchmarking with vbench</title><link href="//vene.ro/blog/memory-benchmarking-with-vbench.html" rel="alternate"/><published>2012-07-05T12:38:00+02:00</published><updated>2012-07-05T12:38:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-07-05:/blog/memory-benchmarking-with-vbench.html</id><summary type="html">&lt;p&gt;The &lt;a href="https://github.com/vene/scikit-learn-speed"&gt;scikit-learn-speed project&lt;/a&gt; now has memory usage&amp;nbsp;benchmarking!&lt;/p&gt;
&lt;p&gt;This was accomplished by building on what I described in my recent
posts, specifically the extensions to Fabian&amp;#8217;s [memory_profiler][] that
you can find in &lt;a href="https://github.com/vene/memory_profiler"&gt;my fork&lt;/a&gt;, but they will be merged upstream soon. The
key element is&amp;nbsp;the &lt;code&gt;%magic_memit&lt;/code&gt; function whose …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The &lt;a href="https://github.com/vene/scikit-learn-speed"&gt;scikit-learn-speed project&lt;/a&gt; now has memory usage&amp;nbsp;benchmarking!&lt;/p&gt;
&lt;p&gt;This was accomplished by building on what I described in my recent
posts, specifically the extensions to Fabian&amp;#8217;s [memory_profiler][] that
you can find in &lt;a href="https://github.com/vene/memory_profiler"&gt;my fork&lt;/a&gt;, but they will be merged upstream soon. The
key element is&amp;nbsp;the &lt;code&gt;%magic_memit&lt;/code&gt; function whose development I blogged
about &lt;a href="http://localhost:8001/2012/06/30/quick-memory-usage-benchmarking-in-ipython/" title="Quick memory usage benchmarking in IPython"&gt;on&lt;/a&gt; &lt;a href="http://localhost:8001/2012/07/02/more-on-memory-benchmarking/" title="More on memory benchmarking"&gt;several&lt;/a&gt; &lt;a href="http://localhost:8001/2012/07/04/on-why-my-memit-fails-on-osx/" title="On why my %memit fails on OSX"&gt;occasions&lt;/a&gt;. I plugged this into &lt;a href="http://wesmckinney.com/blog/?p=373"&gt;vbench&lt;/a&gt;
in a similar way to how the timings are computed, all with great&amp;nbsp;success.&lt;/p&gt;
&lt;p&gt;Here is a screenshot of the way a simple benchmark looks now, with just
a few data&amp;nbsp;points.&lt;/p&gt;
&lt;p&gt;[caption id=&amp;#8221;attachment_464&amp;#8221; align=&amp;#8221;aligncenter&amp;#8221; width=&amp;#8221;600&amp;#8221;][![A
screenshot showing generated output from the scikit-learn-speed project,
illustrating memory usage benchmarking.][]][] Memory benchmarking in
scikit-learn-speed powered by&amp;nbsp;vbench.[/caption]&lt;/p&gt;
&lt;p&gt;You can check it out and use it yourself for your benchmarks, but you
need to use the vbench from the &lt;a href="https://github.com/vene/vbench/tree/memory"&gt;memory branch on my fork&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, there are some important caveats. I am running this on my
laptop, which runs &lt;span class="caps"&gt;OS&lt;/span&gt; X Lion, so, under the effect of &lt;a href="http://localhost:8001/2012/07/04/on-why-my-memit-fails-on-osx/" title="On why my %memit fails on OSX"&gt;this
bug&lt;/a&gt;, I hardcoded the&amp;nbsp;&amp;#8216;&lt;code&gt;-i&lt;/code&gt;&lt;span class="quo"&gt;&amp;#8216;&lt;/span&gt; so the memory benchmarks are not
realistic. Also, the y-range should probably be forced wider, because
the plots look erratic, showing the very small noise at a&amp;nbsp;large-scale.&lt;/p&gt;
&lt;p&gt;[![A screenshot showing generated output from the scikit-learn-speed
  project, illustrating memory usage benchmarking.][]]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2012/07/vbench1.png&lt;/p&gt;</content><category term="benchmarking"/><category term="memit"/><category term="memory"/><category term="vbench"/><category term="python"/><category term="scikit-learn"/></entry><entry><title>On why my %memit fails on OSX</title><link href="//vene.ro/blog/on-why-my-memit-fails-on-osx.html" rel="alternate"/><published>2012-07-04T12:49:00+02:00</published><updated>2012-07-04T12:49:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-07-04:/blog/on-why-my-memit-fails-on-osx.html</id><summary type="html">&lt;p&gt;In my &lt;a href="http://localhost:8001/2012/07/02/more-on-memory-benchmarking/" title="More on memory benchmarking"&gt;last post&lt;/a&gt; I mentioned that I&amp;#8217;m not satisfied with the current
state&amp;nbsp;of &lt;code&gt;%memit&lt;/code&gt;, because some more complicated numerical function
calls make it crash. I will start this post with a reminder of a pretty
important&amp;nbsp;bug:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[On MacOS X (10.7 but maybe more), after forking …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my &lt;a href="http://localhost:8001/2012/07/02/more-on-memory-benchmarking/" title="More on memory benchmarking"&gt;last post&lt;/a&gt; I mentioned that I&amp;#8217;m not satisfied with the current
state&amp;nbsp;of &lt;code&gt;%memit&lt;/code&gt;, because some more complicated numerical function
calls make it crash. I will start this post with a reminder of a pretty
important&amp;nbsp;bug:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[On MacOS X (10.7 but maybe more), after forking a new process, there
is a segfault in Grand Central Dispatch on the &lt;span class="caps"&gt;BLAS&lt;/span&gt; &lt;span class="caps"&gt;DGEMM&lt;/span&gt; function from
Accelerate.][]&lt;br /&gt;
&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;EDIT&lt;/span&gt; 1:&lt;/strong&gt; In a hurry, I forgot to mention how &lt;a href="http://twitter.com/ogrisel/"&gt;Olivier Grisel&lt;/a&gt; and
&lt;a href="https://github.com/cournape"&gt;David Cournapeau&lt;/a&gt; spent some time narrowing down this issue, starting
from an &lt;a href="https://github.com/scikit-learn/scikit-learn/issues/636"&gt;odd testing bug in scikit-learn&lt;/a&gt;. They reported it to Apple,
but there was, as of the date of this post, no&amp;nbsp;reaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;EDIT&lt;/span&gt; 2:&lt;/strong&gt; MinRK &lt;a href="https://twitter.com/minrk/status/228265246819774464" title="Min's tweet"&gt;confirms&lt;/a&gt;, and I verified shortly after, that this
bug is fixed in Mountain Lion (10.8). Still not sure how far back it
goes, though, so feedback is&amp;nbsp;welcome.&lt;/p&gt;
&lt;p&gt;When I first tried to make&amp;nbsp;the &lt;code&gt;%memit&lt;/code&gt; magic, I thought about simply
measuring the current memory, running the command, and measuring the
memory again. The problem is the results are not consistent, because
Python &lt;a href="http://effbot.org/pyfaq/why-doesnt-python-release-the-memory-when-i-delete-a-large-object.htm"&gt;tries to reuse already allocated memory whenever it can&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using memory_profiler, here&amp;#8217;s an example illustrating this elastic
memory management:&lt;br /&gt;
[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
# mem_test.py&lt;br /&gt;
import numpy as&amp;nbsp;np&lt;/p&gt;
&lt;p&gt;def make_a_large_array():&lt;br /&gt;
return np.ones((1000,&amp;nbsp;1000))&lt;/p&gt;
&lt;p&gt;def main():&lt;br /&gt;
make_a_large_array()&lt;br /&gt;
make_a_large_array()&lt;br /&gt;&amp;nbsp;make_a_large_array()&lt;/p&gt;
&lt;p&gt;# in IPython:&lt;br /&gt;
In [1]: import&amp;nbsp;mem_test&lt;/p&gt;
&lt;p&gt;In [2]: %mprun -f mem_test.main mem_test.main()&lt;br /&gt;
Filename:&amp;nbsp;mem_test.py&lt;/p&gt;
&lt;h1 id="line-mem-usage-increment-line-contents"&gt;Line # Mem usage Increment Line Contents&lt;a class="headerlink" href="#line-mem-usage-increment-line-contents" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;8 24.8477 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0000 &lt;span class="caps"&gt;MB&lt;/span&gt; def main():&lt;br /&gt;
9 24.8633 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0156 &lt;span class="caps"&gt;MB&lt;/span&gt; make_a_large_array()&lt;br /&gt;
10 32.4688 &lt;span class="caps"&gt;MB&lt;/span&gt; 7.6055 &lt;span class="caps"&gt;MB&lt;/span&gt; make_a_large_array()&lt;br /&gt;
11 32.4688 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0000 &lt;span class="caps"&gt;MB&lt;/span&gt; make_a_large_array()&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;If this was in an IPython environment, and one would like to see how
much&amp;nbsp;memory &lt;code&gt;make_a_large_array()&lt;/code&gt; uses, you could say we can simply run
it a few times and take the maximum. However, if you happened to
accidentally&amp;nbsp;call &lt;code&gt;main()&lt;/code&gt; once before, you will no longer get a good&amp;nbsp;result:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [3]: %mprun -f mem_test.main mem_test.main()&lt;br /&gt;
Filename:&amp;nbsp;mem_test.py&lt;/p&gt;
&lt;h1 id="line-mem-usage-increment-line-contents_1"&gt;Line # Mem usage Increment Line Contents&lt;a class="headerlink" href="#line-mem-usage-increment-line-contents_1" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;8 32.4922 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0000 &lt;span class="caps"&gt;MB&lt;/span&gt; def main():&lt;br /&gt;
9 32.5234 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0312 &lt;span class="caps"&gt;MB&lt;/span&gt; make_a_large_array()&lt;br /&gt;
10 32.5234 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0000 &lt;span class="caps"&gt;MB&lt;/span&gt; make_a_large_array()&lt;br /&gt;
11 32.5234 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0000 &lt;span class="caps"&gt;MB&lt;/span&gt; make_a_large_array()&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;So how can we get consistent results for the memory usage of an
instruction? We could run it in a fresh, new process. I implemented this
in %memit and it&amp;nbsp;shows:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [5]: %memit mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 8.039062 &lt;span class="caps"&gt;MB&lt;/span&gt; per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [6]: %memit mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 8.035156 &lt;span class="caps"&gt;MB&lt;/span&gt; per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [7]: %memit mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 8.042969 &lt;span class="caps"&gt;MB&lt;/span&gt; per loop&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;This way you can also realistically benchmark&amp;nbsp;assignments:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [8]: %memit X = mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 8.054688 &lt;span class="caps"&gt;MB&lt;/span&gt; per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [9]: %memit X = mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 8.058594 &lt;span class="caps"&gt;MB&lt;/span&gt; per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [10]: %memit X = mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 8.058594 &lt;span class="caps"&gt;MB&lt;/span&gt; per loop&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;If we don&amp;#8217;t spawn a&amp;nbsp;subprocess, &lt;code&gt;del&lt;/code&gt; doesn&amp;#8217;t help, but allocating new
variables does:&lt;br /&gt;
[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [11]: %memit -i X = mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 7.632812 &lt;span class="caps"&gt;MB&lt;/span&gt; per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [12]: del&amp;nbsp;X&lt;/p&gt;
&lt;p&gt;In [13]: %memit -i X = mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 0.000000 &lt;span class="caps"&gt;MB&lt;/span&gt; per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [14]: %memit -i Y = mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 7.632812 &lt;span class="caps"&gt;MB&lt;/span&gt; per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [15]: %memit -i Z = mem_test.make_a_large_array()&lt;br /&gt;
maximum of 3: 7.632812 &lt;span class="caps"&gt;MB&lt;/span&gt; per loop&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Now, the problem is that when the function that you are benchmarking
contains calls&amp;nbsp;to &lt;code&gt;np.dot&lt;/code&gt; (matrix multiplication), the subprocess will
consistently fail with &lt;span class="caps"&gt;SIGSEGV&lt;/span&gt; on affected &lt;span class="caps"&gt;OS&lt;/span&gt; X systems. These are
actually pretty much all the functions that I&amp;nbsp;intended &lt;code&gt;%memit&lt;/code&gt; for:
numerical applications. For that reason, I have&amp;nbsp;made &lt;code&gt;%memit&lt;/code&gt; notify the
user when all subprocesses fail, and to suggest the usage of&amp;nbsp;the &lt;code&gt;-i&lt;/code&gt;
flag.&lt;/p&gt;
&lt;p&gt;I think that, with this&amp;nbsp;update, &lt;code&gt;%memit&lt;/code&gt; is flexible and usable enough
for actual use, and therefore for merging into&amp;nbsp;memory_profiler.&lt;/p&gt;</content><category term="benchmarking"/><category term="IPython"/><category term="magic"/><category term="memit"/><category term="mprun"/><category term="benchmarking"/><category term="python"/></entry><entry><title>More on memory benchmarking</title><link href="//vene.ro/blog/more-on-memory-benchmarking.html" rel="alternate"/><published>2012-07-02T11:27:00+02:00</published><updated>2012-07-02T11:27:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-07-02:/blog/more-on-memory-benchmarking.html</id><summary type="html">&lt;p&gt;Following up on my task to make it easier to benchmark memory usage in
Python, I updated Fabian&amp;#8217;s [memory_profiler][] to include a couple of
useful IPython magics. While in my &lt;a href="http://localhost:8001/2012/06/30/quick-memory-usage-benchmarking-in-ipython/" title="Quick memory usage benchmarking in IPython"&gt;last post&lt;/a&gt;, I used the new IPython
0.13 syntax for defining magics, this time I used the
backwards-compatible …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Following up on my task to make it easier to benchmark memory usage in
Python, I updated Fabian&amp;#8217;s [memory_profiler][] to include a couple of
useful IPython magics. While in my &lt;a href="http://localhost:8001/2012/06/30/quick-memory-usage-benchmarking-in-ipython/" title="Quick memory usage benchmarking in IPython"&gt;last post&lt;/a&gt;, I used the new IPython
0.13 syntax for defining magics, this time I used the
backwards-compatible one from the previous&amp;nbsp;version.&lt;/p&gt;
&lt;p&gt;You can find this work-in-progress as a [pull request on
memory_profiler][] from where you can trace it to my GitHub repo.
Here&amp;#8217;s what you can do with&amp;nbsp;it:&lt;/p&gt;
&lt;h2 id="mprun"&gt;%mprun&lt;a class="headerlink" href="#mprun" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Copying the spirit&amp;nbsp;of &lt;code&gt;%lprun&lt;/code&gt;, since imitation is the most sincere form
of flattery, you can use %mprun to easily view line-by-line memory usage
reports, without having to go in and add&amp;nbsp;the &lt;code&gt;@profile&lt;/code&gt; decorator.&lt;/p&gt;
&lt;p&gt;For&amp;nbsp;example:&lt;/p&gt;
&lt;p&gt;[sourcecode&amp;nbsp;lang=&amp;#8221;python&amp;#8221;]&lt;/p&gt;
&lt;p&gt;In [1]: import numpy as&amp;nbsp;np&lt;/p&gt;
&lt;p&gt;In [2]: from sklearn.linear_model import&amp;nbsp;ridge_regression&lt;/p&gt;
&lt;p&gt;In [3]: X, y = np.array([[1, 2], [3, 4], [5, 6]]), np.array([2, 4,&amp;nbsp;6])&lt;/p&gt;
&lt;p&gt;In [4]: %mprun -f ridge_regression ridge_regression(X, y,&amp;nbsp;1.0)&lt;/p&gt;
&lt;p&gt;(&amp;#8230;)&lt;/p&gt;
&lt;p&gt;109 41.6406 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0000 &lt;span class="caps"&gt;MB&lt;/span&gt; if n_features &amp;gt; n_samples or \&lt;br /&gt;
110 41.6406 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0000 &lt;span class="caps"&gt;MB&lt;/span&gt; isinstance(sample_weight, np.ndarray) or \&lt;br /&gt;
111 41.6406 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0000 &lt;span class="caps"&gt;MB&lt;/span&gt; sample_weight != 1.0:&lt;br /&gt;
112&lt;br /&gt;
113 # kernel ridge&lt;br /&gt;
114 # w = X.T * inv(X X\^t + alpha*Id) y&lt;br /&gt;
115 A = np.dot(X, X.T)&lt;br /&gt;
116 A.flat[::n_samples + 1] += alpha * sample_weight&lt;br /&gt;
117 coef = np.dot(X.T, _solve(A, y, solver, tol))&lt;br /&gt;
118 else:&lt;br /&gt;
119 # ridge&lt;br /&gt;
120 # w = inv(X\^t X + alpha*Id) * X.T y&lt;br /&gt;
121 41.6484 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0078 &lt;span class="caps"&gt;MB&lt;/span&gt; A = np.dot(X.T, X)&lt;br /&gt;
122 41.6875 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0391 &lt;span class="caps"&gt;MB&lt;/span&gt; A.flat[::n_features + 1] += alpha&lt;br /&gt;
123 41.7344 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0469 &lt;span class="caps"&gt;MB&lt;/span&gt; coef = _solve(A, np.dot(X.T, y), solver,
tol)&lt;br /&gt;
124&lt;br /&gt;
125 41.7344 &lt;span class="caps"&gt;MB&lt;/span&gt; 0.0000 &lt;span class="caps"&gt;MB&lt;/span&gt; return&amp;nbsp;coef.T&lt;/p&gt;
&lt;p&gt;[/sourcecode]&lt;/p&gt;
&lt;h2 id="memit"&gt;%memit&lt;a class="headerlink" href="#memit" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As described in my previous post, this is&amp;nbsp;a &lt;code&gt;%timeit&lt;/code&gt;-like magic for
quickly seeing how much memory a Python command uses.&lt;br /&gt;
Unlike %timeit, however, the command needs to be executed in a fresh
process. I have to dig in some more to debug this, but if the command is
run in the current process, very often the difference in memory usage
will be insignificant, I assume because preallocated memory is used. The
problem is that when running in a new process, some functions that I
tried to bench crash&amp;nbsp;with &lt;code&gt;SIGSEGV&lt;/code&gt;. For a lot of stuff,&amp;nbsp;though,
&lt;code&gt;%memit&lt;/code&gt; is currently&amp;nbsp;usable:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [1]: import numpy as&amp;nbsp;np&lt;/p&gt;
&lt;p&gt;In [2]: X = np.ones((1000,&amp;nbsp;1000))&lt;/p&gt;
&lt;p&gt;In [3]: %memit X.T&lt;br /&gt;
worst of 3: 0.242188 &lt;span class="caps"&gt;MB&lt;/span&gt; per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [4]: %memit np.asfortranarray(X)&lt;br /&gt;
worst of 3: 15.687500 &lt;span class="caps"&gt;MB&lt;/span&gt; per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [5]: Y =&amp;nbsp;X.copy(&amp;#8216;F&amp;#8217;)&lt;/p&gt;
&lt;p&gt;In [6]: %memit np.asfortranarray(Y)&lt;br /&gt;
worst of 3: 0.324219 &lt;span class="caps"&gt;MB&lt;/span&gt; per loop&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;It is very easy, using this small tool, to see what forces memory
copying and what does&amp;nbsp;not.&lt;/p&gt;
&lt;h2 id="installation-instructions"&gt;Installation instructions&lt;a class="headerlink" href="#installation-instructions" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, you have to get the source code of this version of
memory_profiler. Then, it depends on your version of IPython. If you
have 0.10, you have to&amp;nbsp;edit &lt;code&gt;~/.ipython/ipy_user_conf.py&lt;/code&gt; like this:
(once again, instructions &lt;em&gt;borrowed&lt;/em&gt; from&amp;nbsp;[line_profiler][])&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
# These two lines are standard and probably already there.&lt;br /&gt;
import IPython.ipapi&lt;br /&gt;
ip =&amp;nbsp;IPython.ipapi.get()&lt;/p&gt;
&lt;p&gt;# These two are the important ones.&lt;br /&gt;
import memory_profiler&lt;br /&gt;
ip.expose_magic(&amp;#8216;mprun&amp;#8217;, memory_profiler.magic_mprun)&lt;br /&gt;
ip.expose_magic(&amp;#8216;memit&amp;#8217;, memory_profiler.magic_memit)&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re using IPython 0.11 or newer, the steps are different. First
create a configuration profile:&lt;br /&gt;
[sourcecode lang=&amp;#8221;bash&amp;#8221;]&lt;br /&gt;
$ ipython profile create&lt;br /&gt;
[/sourcecode]&lt;br /&gt;
Then create a file&amp;nbsp;named &lt;code&gt;~/.ipython/extensions/memory_profiler_ext.py&lt;/code&gt;
with the following&amp;nbsp;content:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
import&amp;nbsp;memory_profiler&lt;/p&gt;
&lt;p&gt;def load_ipython_extension(ip):&lt;br /&gt;
ip.define_magic(&amp;#8216;mprun&amp;#8217;, memory_profiler.magic_mprun)&lt;br /&gt;
ip.define_magic(&amp;#8216;memit&amp;#8217;, memory_profiler.magic_memit)&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Then register it&amp;nbsp;in &lt;code&gt;~/.ipython/profile_default/ipython_config.py&lt;/code&gt;, like
this. Of course, if you already have other extensions such&amp;nbsp;as
&lt;code&gt;line_profiler_ext&lt;/code&gt;, just add the new one to the&amp;nbsp;list.&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
c.TerminalIPythonApp.extensions = [&lt;br /&gt;
&amp;#8216;memory_profiler_ext&amp;#8217;,&lt;br /&gt;
]&lt;br /&gt;
c.InteractiveShellApp.extensions = [&lt;br /&gt;
&amp;#8216;memory_profiler_ext&amp;#8217;,&lt;br /&gt;
]&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Now launch IPython and you can use the new magics like in the examples&amp;nbsp;above.&lt;/p&gt;</content><category term="benchmarking"/><category term="IPython"/><category term="magic"/><category term="memit"/><category term="memory"/><category term="memory_profiler"/><category term="mprun"/><category term="benchmarking"/><category term="python"/></entry><entry><title>Quick memory usage benchmarking in IPython</title><link href="//vene.ro/blog/quick-memory-usage-benchmarking-in-ipython.html" rel="alternate"/><published>2012-06-30T08:53:00+02:00</published><updated>2012-06-30T08:53:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-06-30:/blog/quick-memory-usage-benchmarking-in-ipython.html</id><summary type="html">&lt;p&gt;Everybody&amp;nbsp;loves &lt;code&gt;%timeit&lt;/code&gt;, there&amp;#8217;s no doubt about it. So why not have
something like that, but for measuring how much memory your line takes?
Well, now you can; grab a hold of the script in the following gist and
run it like in the&amp;nbsp;example.&lt;/p&gt;
&lt;p&gt;[gist&amp;nbsp;id=3022718]&lt;/p&gt;
&lt;p&gt;Instead …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Everybody&amp;nbsp;loves &lt;code&gt;%timeit&lt;/code&gt;, there&amp;#8217;s no doubt about it. So why not have
something like that, but for measuring how much memory your line takes?
Well, now you can; grab a hold of the script in the following gist and
run it like in the&amp;nbsp;example.&lt;/p&gt;
&lt;p&gt;[gist&amp;nbsp;id=3022718]&lt;/p&gt;
&lt;p&gt;Instead of taking care of the dirty process inspection stuff myself, I
decided to delegate this to Fabian&amp;#8217;s simple but very good&amp;nbsp;[&lt;code&gt;memory_profiler&lt;/code&gt;][]. There is also &lt;a href="http://guppy-pe.sourceforge.net/"&gt;Guppy&lt;/a&gt; available, but its design
seems a bit and overkill for this&amp;nbsp;task.&lt;/p&gt;
&lt;p&gt;Please contact me if you find problems with this implementation, this is
a preliminary, quick hack-y version.&amp;nbsp;:)&lt;/p&gt;</content><category term="benchmarking"/><category term="benchmark"/><category term="IPython"/><category term="magic"/><category term="memory"/><category term="memory_profiler"/><category term="profiling"/><category term="benchmarking"/><category term="python"/></entry><entry><title>Compiling and Installing GLARF and the bundled Charniak parser on MacOS X</title><link href="//vene.ro/blog/compiling-and-installing-glarf-and-the-bundled-charniak-parser-on-macos-x.html" rel="alternate"/><published>2012-06-21T12:32:00+02:00</published><updated>2012-06-21T12:32:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-06-21:/blog/compiling-and-installing-glarf-and-the-bundled-charniak-parser-on-macos-x.html</id><summary type="html">&lt;p&gt;It seems that I keep getting handed buggy code to install. These are
cases of research software where the developers didn&amp;#8217;t make the effort
to make sure their tool works on the platforms it&amp;nbsp;should.&lt;/p&gt;
&lt;p&gt;[&lt;span class="caps"&gt;GLARF&lt;/span&gt;][] (Grammatical and Logical Argument Representation Framework)
is, in their words, &amp;#8220;a typed feature …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It seems that I keep getting handed buggy code to install. These are
cases of research software where the developers didn&amp;#8217;t make the effort
to make sure their tool works on the platforms it&amp;nbsp;should.&lt;/p&gt;
&lt;p&gt;[&lt;span class="caps"&gt;GLARF&lt;/span&gt;][] (Grammatical and Logical Argument Representation Framework)
is, in their words, &amp;#8220;a typed feature structure framework for
representing regularizations of parse trees&amp;#8221;. It is a processing
pipeline from &lt;span class="caps"&gt;NYU&lt;/span&gt; with rich output including many types of structure in
the given text. However, it is clearly a case of software whose
maintenance was abandoned when it &amp;#8220;worked&amp;#8221; for them. The whole install
and run procedure is pretty messy, but at least it&amp;#8217;s documented. The
problem is, following it step by step doesn&amp;#8217;t work on my MacBook. As
usual, I needed to hack through it a&amp;nbsp;bit.&lt;/p&gt;
&lt;p&gt;The Charniak parser distributed with &lt;span class="caps"&gt;GLARF&lt;/span&gt; has now been superseded by
the [&lt;span class="caps"&gt;BLLIP&lt;/span&gt; parser][]. The new one is tricky to compile as well, but I
have yet to see if it plugs into &lt;span class="caps"&gt;GLARF&lt;/span&gt;, so I leave this for a future&amp;nbsp;post.&lt;/p&gt;
&lt;p&gt;Here are the steps I needed to take to make &lt;span class="caps"&gt;GLARF&lt;/span&gt;&amp;nbsp;work:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
Download and unzip the &lt;span class="caps"&gt;GLARF&lt;/span&gt;&amp;nbsp;package.

&lt;/li&gt;
&lt;li&gt;
Make sure you have `sbcl` in your path, and your `perl` is in (or linked
from)&amp;nbsp;`/usr/bin/perl`

&lt;/li&gt;
&lt;li&gt;
Set the environment variables. I like to make a shell script to set them
so I don&amp;#8217;t have to do it every time. So I write something looking like
this:

[sourcecode lang=&amp;#8221;bash&amp;#8221;]  
\# glarf\_env.sh

export &lt;span class="caps"&gt;GLARF&lt;/span&gt;=/Users/vene/code/&lt;span class="caps"&gt;GLARF&lt;/span&gt;  
export &lt;span class="caps"&gt;GLARF&lt;/span&gt;\_JET=\${&lt;span class="caps"&gt;GLARF&lt;/span&gt;}/&lt;span class="caps"&gt;JET&lt;/span&gt;  
export &lt;span class="caps"&gt;PATH&lt;/span&gt;=\$&lt;span class="caps"&gt;PATH&lt;/span&gt;:.  
[/sourcecode]

&lt;p&gt;
Then for every session when I want to use &lt;span class="caps"&gt;GLARF&lt;/span&gt;, I do
`source&amp;nbsp;glarf_env.sh`.

&lt;/li&gt;
&lt;li&gt;
Compile &lt;span class="caps"&gt;GLARF&lt;/span&gt; by running `$&lt;span class="caps"&gt;GLARF&lt;/span&gt;/commands-2010/compile-glarf`. *Note:*
this only compiles the pipeline lisp&amp;nbsp;code.

&lt;/li&gt;
&lt;li&gt;
Now, according to their instructions, you&amp;#8217;re done. However, if you try
to run it, you&amp;#8217;d notice the output is incomplete. (It goes through the
named entity extraction part, but it doesn&amp;#8217;t run the parser.) The reason
for this is that they distribute the Charniak parser with a precompiled
binary that runs on Linux, but not on the Mac. So we need to recompile
it. So go to `$&lt;span class="caps"&gt;GLARF&lt;/span&gt;/charniak-parser-2005/parser05Aug16-static/&lt;span class="caps"&gt;PARSE2&lt;/span&gt; `,
run `make clean` and roll up your&amp;nbsp;sleeves.

&lt;/li&gt;
&lt;li&gt;
Obviously, simply running `make` doesn&amp;#8217;t work. [As documented by Pawel
Mazur][], we need to edit `BchartSm.C` to add the line
`#include&amp;nbsp;&amp;#8220;GotIter.h&amp;#8221;`

&lt;/li&gt;
&lt;li&gt;
On my system this still isn&amp;#8217;t enough, and I get some linker errors. By
poking through the Makefile, I noticed I could fix it by commenting out
the 5th line: `&lt;span class="caps"&gt;LDFLAGS&lt;/span&gt;=-static`.

&lt;/li&gt;
&lt;li&gt;
Now run make and watch it work, hurrah!&amp;nbsp;&amp;#92;o/

&lt;/li&gt;
&lt;li&gt;
To see if &lt;span class="caps"&gt;GLARF&lt;/span&gt; itself works now, go to
`$&lt;span class="caps"&gt;GLARF&lt;/span&gt;/commands-2010/run-glarf/` and run
`make-all-glarf-a sample-files-a N`. You should get beautiful, beautiful
&lt;span class="caps"&gt;GLARF&lt;/span&gt; output&amp;nbsp;files.

&lt;/li&gt;
&lt;/ul&gt;
Phew, now that was quite an effort!

  [&lt;span class="caps"&gt;GLARF&lt;/span&gt;]: http://nlp.cs.nyu.edu/meyers/&lt;span class="caps"&gt;GLARF&lt;/span&gt;.html
  [&lt;span class="caps"&gt;BLLIP&lt;/span&gt; parser]: https://github.com/&lt;span class="caps"&gt;BLLIP&lt;/span&gt;/bllip-parser
  [As documented by Pawel Mazur]:&amp;nbsp;http://web.science.mq.edu.au/~mpawel/resources/notes/compilingCharniakJohnson.htm</content><category term="nlp"/><category term="bllip"/><category term="charniak"/><category term="glarf"/><category term="installation"/><category term="parser"/><category term="nlp"/></entry><entry><title>Compiling MegaM on MacOS X</title><link href="//vene.ro/blog/compiling-megam-on-macos-x.html" rel="alternate"/><published>2012-06-08T11:45:00+02:00</published><updated>2012-06-08T11:45:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-06-08:/blog/compiling-megam-on-macos-x.html</id><summary type="html">&lt;p&gt;&lt;a href="http://hal3.name/megam"&gt;MegaM&lt;/a&gt; is Hal Daumé &lt;span class="caps"&gt;III&lt;/span&gt;&amp;#8217;s maxent (logistic regression, and much more)
modeling software written in OCaml. It is feature-packed and seems to be
used a lot, despite being slightly dated. &lt;a href="http://nltk.org" title="Natural Language Toolkit"&gt;&lt;span class="caps"&gt;NLTK&lt;/span&gt;&lt;/a&gt; is able to use&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;In order to compile it as of 2012, with the current version of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://hal3.name/megam"&gt;MegaM&lt;/a&gt; is Hal Daumé &lt;span class="caps"&gt;III&lt;/span&gt;&amp;#8217;s maxent (logistic regression, and much more)
modeling software written in OCaml. It is feature-packed and seems to be
used a lot, despite being slightly dated. &lt;a href="http://nltk.org" title="Natural Language Toolkit"&gt;&lt;span class="caps"&gt;NLTK&lt;/span&gt;&lt;/a&gt; is able to use&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;In order to compile it as of 2012, with the current version of OCaml, I
had to do some tricks that I would like to document here. It&amp;#8217;s no big
deal but it could save somebody precious&amp;nbsp;minutes.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download and unpack the gzip&amp;nbsp;archive.&lt;/li&gt;
&lt;li&gt;Install ocaml using&amp;nbsp;macports: &lt;code&gt;sudo port install ocaml&lt;/code&gt;. &lt;em&gt;Note:&lt;/em&gt;
    this installed version 3.12.1_5, &lt;span class="caps"&gt;YMMV&lt;/span&gt; with newer versions&amp;nbsp;later.&lt;/li&gt;
&lt;li&gt;Point the compiler to the correct headers. First&amp;nbsp;run &lt;code&gt;ocamlc -where&lt;/code&gt;
    to find out the correct path. On my system&amp;nbsp;it&amp;#8217;s
    &lt;code&gt;/opt/local/lib/ocaml/caml&lt;/code&gt;. Change&amp;nbsp;the &lt;code&gt;WITHCLIBS&lt;/code&gt; line (#73) in
    the Makefile to point&amp;nbsp;there.&lt;/li&gt;
&lt;li&gt;As of OCaml 3.12.0,&amp;nbsp;the &lt;code&gt;-lstr&lt;/code&gt; compiler flag should be replaced&amp;nbsp;with &lt;code&gt;-lcamlstr&lt;/code&gt;. It occurs on line #62 within the definition&amp;nbsp;of
    &lt;code&gt;WITHSTR&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;make&lt;/code&gt; or &lt;code&gt;make opt&lt;/code&gt; and&amp;nbsp;enjoy.&lt;/li&gt;
&lt;/ol&gt;</content><category term="nlp"/><category term="compile"/><category term="install"/><category term="maxent"/><category term="megam"/><category term="nlp"/></entry><entry><title>Dynamically generated benchmarks with vbench</title><link href="//vene.ro/blog/dynamically-generated-benchmarks-with-vbench.html" rel="alternate"/><published>2012-06-07T01:57:00+02:00</published><updated>2012-06-07T01:57:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-06-07:/blog/dynamically-generated-benchmarks-with-vbench.html</id><summary type="html">&lt;p&gt;To construct&amp;nbsp;a &lt;code&gt;vbench&lt;/code&gt; benchmark you need a setup string and a code
string. The constructor&amp;#8217;s signature&amp;nbsp;is:  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;Benchmark(self, code, setup, ncalls=None, repeat=3, cleanup=None, name=None, description=None, start_date=None, logy=False)&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="why-generate-benchmarks-dynamically"&gt;Why generate benchmarks dynamically?&lt;a class="headerlink" href="#why-generate-benchmarks-dynamically" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For&amp;nbsp;most &lt;code&gt;scikit-learn&lt;/code&gt; purposes,&amp;nbsp;the &lt;code&gt;code&lt;/code&gt; string will be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;To construct&amp;nbsp;a &lt;code&gt;vbench&lt;/code&gt; benchmark you need a setup string and a code
string. The constructor&amp;#8217;s signature&amp;nbsp;is:  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;Benchmark(self, code, setup, ncalls=None, repeat=3, cleanup=None, name=None, description=None, start_date=None, logy=False)&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="why-generate-benchmarks-dynamically"&gt;Why generate benchmarks dynamically?&lt;a class="headerlink" href="#why-generate-benchmarks-dynamically" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For&amp;nbsp;most &lt;code&gt;scikit-learn&lt;/code&gt; purposes,&amp;nbsp;the &lt;code&gt;code&lt;/code&gt; string will be very close&amp;nbsp;to &lt;code&gt;"algorithm.fit(X, y)"&lt;/code&gt;, &lt;code&gt;"algorithm.transform(X)"&lt;/code&gt; or
&lt;code&gt;"algorithm.predict(X)"&lt;/code&gt;. We can generate a lot of benchmarks by
changing what the algorithm is, and changing what the data is or the way
it is&amp;nbsp;generated.&lt;/p&gt;
&lt;p&gt;A possible idea would be to create a
&lt;abbr title="domain-specific language" lang="en"&gt;&lt;span class="caps"&gt;DSL&lt;/span&gt;&lt;/abbr&gt; in which to
specify scikit-learn tests and create benchmarks from them. However,
before engineering such a solution, I wanted to test out how to generate
three related benchmarks using different arguments for the dataset
generation&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;This is what I came up&amp;nbsp;with:&lt;/p&gt;
&lt;p&gt;[sourcecode language=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
from vbench.benchmark import&amp;nbsp;Benchmark&lt;/p&gt;
&lt;p&gt;_setup = &amp;#8220;&amp;#8221;&amp;#8220;&lt;br /&gt;
from deps import&amp;nbsp;*&lt;/p&gt;
&lt;p&gt;kwargs = %s&lt;br /&gt;
X, y = make_regression(random_state=0, **kwargs)&lt;br /&gt;
lr = LinearRegression()&lt;br /&gt;&amp;nbsp;&amp;#8220;&amp;#8221;&amp;#8220;&lt;/p&gt;
&lt;p&gt;_configurations = [&lt;br /&gt;
(&amp;#8216;linear_regression_many_samples&amp;#8217;,&lt;br /&gt;
{&amp;#8216;n_samples&amp;#8217;: 10000, &amp;#8216;n_features&amp;#8217;: 100}),&lt;br /&gt;
(&amp;#8216;linear_regression_many_features&amp;#8217;,&lt;br /&gt;
{&amp;#8216;n_samples&amp;#8217;: 100, &amp;#8216;n_features&amp;#8217;: 10000}),&lt;br /&gt;
(&amp;#8216;linear_regression_many_targets&amp;#8217;,&lt;br /&gt;
{&amp;#8216;n_samples&amp;#8217;: 1000, &amp;#8216;n_features&amp;#8217;: 100, &amp;#8216;n_targets&amp;#8217;: 100})&lt;br /&gt;&amp;nbsp;]&lt;/p&gt;
&lt;p&gt;_statement = &amp;#8220;lr.fit(X,&amp;nbsp;y)&amp;#8221;&lt;/p&gt;
&lt;p&gt;_globs = globals()&lt;br /&gt;
_globs.update({name: Benchmark(_statement, _setup % str(kwargs),
name=name)&lt;br /&gt;
for name, kwargs in&amp;nbsp;_configurations})&lt;/p&gt;
&lt;p&gt;[/sourcecode]&lt;/p&gt;
&lt;p&gt;It works perfectly, but I don&amp;#8217;t like having to hack the globals to make
the benchmarks detectable. This is because of the way the vbench suite
gathers benchmarks.&amp;nbsp;In &lt;code&gt;__init__.py&lt;/code&gt; we have to&amp;nbsp;do
&lt;code&gt;from linear_regression import *&lt;/code&gt;. With a small update to the detection
method, we could replace the hacky part with a public lists of Benchmark&amp;nbsp;objects.&lt;/p&gt;
&lt;h2 id="exposed-issues"&gt;Exposed issues&lt;a class="headerlink" href="#exposed-issues" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While working on this, after my first attempt, I was surprised to see
that there were no results added to the database, and output plots were
empty. It turns out that the generated benchmarks weren&amp;#8217;t running, even
though if I copied and pasted their source code from the generated html,
it would run. Vbench was not issuing any sort of message to let me know
that anything was&amp;nbsp;wrong.&lt;/p&gt;
&lt;p&gt;So what was the problem? My fault, of course, whitespace. But in all
fairness, we should add better&amp;nbsp;feedback.&lt;/p&gt;
&lt;p&gt;This is what I was doing to generate the setup&amp;nbsp;string:&lt;/p&gt;
&lt;p&gt;[sourcecode lang=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
def _make_setup(kwargs):&lt;br /&gt;
return &amp;#8220;&amp;#8221;&amp;#8220;&lt;br /&gt;
from deps import&amp;nbsp;*&lt;/p&gt;
&lt;p&gt;kwargs = %s&lt;br /&gt;
X, y = make_regression(random_state=0, **kwargs)&lt;br /&gt;
lr = LinearRegression()&lt;br /&gt;
&amp;#8220;&amp;#8221;&amp;#8221; % str(kwargs)&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s clear as daylight now that I overzealously indented the multiline
string. But man, was it hard to debug! Also, in this example, the bug
led to a refactoring that made the whole thing nicer and more direct.
Hopefully, my experience with vbench will lead to some improvements to
this cool and highly useful piece of&amp;nbsp;software.&lt;/p&gt;</content><category term="scikit-learn"/><category term="gsoc"/><category term="vbench"/><category term="benchmarking"/><category term="python"/></entry><entry><title>First contact with vbench</title><link href="//vene.ro/blog/first-contact-with-vbench.html" rel="alternate"/><published>2012-05-29T12:57:00+02:00</published><updated>2012-05-29T12:57:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-05-29:/blog/first-contact-with-vbench.html</id><summary type="html">&lt;p&gt;With a slight delay caused by going to lovely lovely Istanbul for the
&lt;span class="caps"&gt;LREC&lt;/span&gt; conference where I presented a &lt;a href="http://vene.ro/papers/lrec12-poster.pdf"&gt;poster&lt;/a&gt;, I am back to work on the
Google Summer of Code project. By the way, this year&amp;#8217;s logo and swag
looks a lot nicer than last year&amp;#8217;s, thank …&lt;/p&gt;</summary><content type="html">&lt;p&gt;With a slight delay caused by going to lovely lovely Istanbul for the
&lt;span class="caps"&gt;LREC&lt;/span&gt; conference where I presented a &lt;a href="http://vene.ro/papers/lrec12-poster.pdf"&gt;poster&lt;/a&gt;, I am back to work on the
Google Summer of Code project. By the way, this year&amp;#8217;s logo and swag
looks a lot nicer than last year&amp;#8217;s, thank you Google!&lt;br /&gt;
[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2012/05/P5280194-300x225.jpg" title="GSoC swag" /&gt;]&lt;a href="http://localhost:8001/wp-content/uploads/2012/05/P5280194-300x225.jpg" title="GSoC swag"&gt;&lt;/a&gt;&lt;br /&gt;
The backbone of my GSoC consists of putting together a continuous
benchmark platform. I took a good look at &lt;a href="https://github.com/pydata/vbench"&gt;vbench&lt;/a&gt; and spent an
evening hacking Wes&amp;#8217;s benchmarks suite config into something that will
run on my machine. These are the key points I got from this&amp;nbsp;experience.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vbench is, at least for the moment, very specific to &lt;a href="http://pandas.pydata.org/pandas-docs/vbench/"&gt;Wes&amp;#8217; and
    Pandas&amp;#8217; needs&lt;/a&gt;. This is also because there weren&amp;#8217;t so many other
    users that could have brought&amp;nbsp;contributions.&lt;/li&gt;
&lt;li&gt;Even though it has support for some configuration and automation,
    vbench seems largely suited for running on a local machine.
    Specifically, it is &lt;span class="caps"&gt;NOT&lt;/span&gt; designed to run continuously but in one-off
    runs, going back in git history and getting the last commit for each
    day, and running the benchmark with it. Of course, it is trivial to
    patch it into getting just one&amp;nbsp;commit.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;code-as-strings&lt;/em&gt; approach is not ideal. The first thought is
    that it should be replaced with&amp;nbsp;reading &lt;code&gt;.py&lt;/code&gt; files into strings,
    but there are two issues with this:&lt;ol&gt;
&lt;li&gt;One benchmark file can have a lot of setup code and several key
    lines that need to actually be benched. This can be fixed using
    convensions (ie. setup functions&amp;nbsp;and &lt;code&gt;bench_*&lt;/code&gt; functions) in the
    spirit of testing suites, or using&amp;nbsp;decorators.&lt;/li&gt;
&lt;li&gt;I would like to be able to run bench files as python scripts,
    but the vbench import system breaks this. This can be fixed by
    hijacking the imports when reading the&amp;nbsp;file.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our project has different dynamics than Pandas, so it&amp;#8217;s important that
the published results run on an independent machine, but it would be
great if an individual developer can run the benchmark himself while
coding but before pushing his changes upstream. Of course, his numbers
would only be comparable to the numbers he gets on his own machine
before his changes, but a developer shouldn&amp;#8217;t wait for the daily
benchmark for knowing if he made an&amp;nbsp;improvement.&lt;/p&gt;
&lt;p&gt;On the other hand there is &lt;a href="http://code.google.com/p/unladen-swallow/"&gt;unladen-swallow&lt;/a&gt;&amp;#8216;s &lt;a href="http://code.google.com/p/unladen-swallow/wiki/Benchmarks"&gt;benchmark system&lt;/a&gt;
using the&amp;nbsp;[&lt;code&gt;perf.py&lt;/code&gt;]&lt;a href="http://localhost:8001/wp-content/uploads/2012/05/P5280194-300x225.jpg" title="GSoC swag"&gt;&lt;/a&gt; file. I didn&amp;#8217;t try it out yet, so I would like
feedback, but there are some key things that can be taken from&amp;nbsp;them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Memory usage&amp;nbsp;benchmarking&lt;/li&gt;
&lt;li&gt;Python scripts as benchmarks, with a simple but efficient Benchmark
    object&amp;nbsp;hierarchy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What&amp;#8217;s missing&amp;nbsp;is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A system to remember previous results and compare them, similar to
    vbench&amp;#8217;s&amp;nbsp;database&lt;/li&gt;
&lt;li&gt;The ability to bench only an area of the code without rerunning the
    setup. (Not really sure whether vbench&amp;#8217;s way is actually&amp;nbsp;better)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At a first glance, it seems that a very good system can be obtained by
combining these two excellent projects (or rather, improving vbench with
features&amp;nbsp;from &lt;code&gt;perf.py&lt;/code&gt;). While I continue exploring this, I would like
to hear feedback from people who had to do with similar issues. As for
the GSoC timeline, I plan to join forces with Immanuel and design a
solid benchmark suite for the linear models over the next 2&amp;nbsp;weeks.&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2012/05/P5280194-300x225.jpg" title="GSoC swag" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2012/05/P5280194.jpg&lt;/p&gt;</content><category term="scikit-learn"/><category term="benchmarks"/><category term="perf.py"/><category term="performance"/><category term="vbench"/><category term="scikit-learn"/></entry><entry><title>Support vector regression on Anscombe’s dataset</title><link href="//vene.ro/blog/support-vector-regression-on-anscombes-dataset.html" rel="alternate"/><published>2012-05-27T21:59:00+02:00</published><updated>2012-05-27T21:59:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-05-27:/blog/support-vector-regression-on-anscombes-dataset.html</id><summary type="html">&lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Anscombe's_quartet" title="Anscombe's quartet"&gt;Anscombe&amp;#8217;s quartet&lt;/a&gt; is a set of four toy datasets that look very
different, but many of their statistics coincide. They were developed by
Francis Anscombe as a striking visual to show that even for small
datasets, blindly examining their statistical properties without
considering their structure can&amp;nbsp;mislead.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Anscombe's datasets" src="http://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Anscombe%27s_quartet_3.svg/640px-Anscombe%27s_quartet_3.svg.png" /&gt;&lt;/p&gt;
&lt;p&gt;Particularly, the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Anscombe's_quartet" title="Anscombe's quartet"&gt;Anscombe&amp;#8217;s quartet&lt;/a&gt; is a set of four toy datasets that look very
different, but many of their statistics coincide. They were developed by
Francis Anscombe as a striking visual to show that even for small
datasets, blindly examining their statistical properties without
considering their structure can&amp;nbsp;mislead.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Anscombe's datasets" src="http://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Anscombe%27s_quartet_3.svg/640px-Anscombe%27s_quartet_3.svg.png" /&gt;&lt;/p&gt;
&lt;p&gt;Particularly, the four datasets have the same &lt;a href="http://scikit-learn.org/stable/modules/linear_model.html#ordinary-least-squares" title="Ordinary least squares regression"&gt;least squares regression
line&lt;/a&gt;. While the second dataset is a clear example of a nonlinear
correlation which cannot be accurately captured by any linear model, the
third dataset is actually perfectly linear, with no noise, but just a
single outlier that shifts the regression line&amp;nbsp;considerably.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://scikit-learn.org/stable/modules/svm.html#regression" title="Support vector regression"&gt;Support vector regression&lt;/a&gt; is an extension of the support vector
machine idea to tackle the regression problem. It is based on the
observation that a &lt;span class="caps"&gt;SVM&lt;/span&gt; classifier builds its decision boundary as a
function of a (small) subset of training points. For regression, &lt;span class="caps"&gt;SVR&lt;/span&gt;
fits a &lt;em&gt;tube&lt;/em&gt; that is robust to noise within a width
[latex]\epsilon[/latex]. For this particular example, using a small
width makes the solution robust to the obvious outlier. For very small
but non-zero [latex]\epsilon[/latex], the solution is a combination of
the outlier and on two other points. For [latex]\epsilon=0[/latex], you
can see that every point except a non-outlier is highlighted. This is
actually the perfect solution but very&amp;nbsp;dense.&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Support vector regression on Anscombe's third dataset with no
noise" src="http://localhost:8001/wp-content/uploads/2012/05/svr.gif" title="SVR on Anscombe's dataset with no noise" /&gt;][]&lt;/p&gt;
&lt;p&gt;Every frame displays the global mean squared error and the true mean
squared error, &lt;em&gt;i.e.&lt;/em&gt; over the inlying points. If the epsilon size is
well chosen, &lt;span class="caps"&gt;SVR&lt;/span&gt; can perform robustly with a sparse solution. Since our
interest was in avoiding the outlier, we assumed no noise in the inlying
data, so a very small epsilon is perfect. For real data a larger epsilon
is often useful because of variability in the data. When adding noise,
&lt;span class="caps"&gt;SVR&lt;/span&gt; still manages to avoid the outlier, but when the tube width becomes
zero, the solution is again very dense, very&amp;nbsp;non-parametric.&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Support vector regression on Anscombe's third dataset with
noise" src="http://localhost:8001/wp-content/uploads/2012/05/svr_noise.gif" title="SVR on Anscombe's dataset with noise" /&gt;][]&lt;/p&gt;
&lt;p&gt;Here is the code you can use to play around with&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;[gist&amp;nbsp;id=2815589]&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Support vector regression on Anscombe's third dataset with no
  noise" src="http://localhost:8001/wp-content/uploads/2012/05/svr.gif" title="SVR on Anscombe's dataset with no noise" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2012/05/svr.gif&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Support vector regression on Anscombe's third dataset with
  noise" src="http://localhost:8001/wp-content/uploads/2012/05/svr_noise.gif" title="SVR on Anscombe's dataset with noise" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2012/05/svr_noise.gif&lt;/p&gt;</content><category term="scikit-learn"/><category term="anscombe"/><category term="outlier"/><category term="robust regression"/><category term="support vector regression"/><category term="svm"/><category term="svr"/><category term="python"/><category term="scikit-learn"/></entry><entry><title>GSoC 2012 proposal: Need for scikit-learn speed</title><link href="//vene.ro/blog/gsoc-2012-proposal-need-for-scikit-learn-speed.html" rel="alternate"/><published>2012-04-16T00:37:00+02:00</published><updated>2012-04-16T00:37:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-04-16:/blog/gsoc-2012-proposal-need-for-scikit-learn-speed.html</id><summary type="html">&lt;p&gt;This summer I hope to be able to put in another full-time amount of
effort into scikit-learn. After a successful Google Summer of Code
project last year on dictionary learning, I now plan to do some
low-level work. The title of my proposal is: &amp;#8220;Need for scikit-learn
speed&amp;#8221; and, in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This summer I hope to be able to put in another full-time amount of
effort into scikit-learn. After a successful Google Summer of Code
project last year on dictionary learning, I now plan to do some
low-level work. The title of my proposal is: &amp;#8220;Need for scikit-learn
speed&amp;#8221; and, in a nutshell, will make the scikit go faster and will help
it stay that&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;Scikit-learn has always enforced standards of quality that kept all
implementations at a non-trivial level (i.e. faster than using &lt;a href="http://docs.scipy.org/doc/scipy/reference/optimize.html"&gt;the
generic optimizers in scipy&lt;/a&gt;). However, not all modules are equal:
some have received more attention for speed than others (for example the
&lt;span class="caps"&gt;SGD&lt;/span&gt; classes). I intend to raise the bar towards a more uniform&amp;nbsp;level.&lt;/p&gt;
&lt;h2 id="are-you-crazy-can-you-really-do-this"&gt;Are you crazy, can you really do this?&lt;a class="headerlink" href="#are-you-crazy-can-you-really-do-this" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Well, of course. This might not the usual GSoC proposal, but I can show
how I can do it and how it&amp;#8217;s easily quantifiable. Actually, a very
important part of the work will be to make scikit-learn&amp;#8217;s speed easily&amp;nbsp;measurable.&lt;/p&gt;
&lt;p&gt;As for the specific speed-ups, I have shown &lt;a href="http://localhost:8001/2011/08/07/optimizing-orthogonal-matching-pursuit-code-in-numpy-part-1/" title="Optimizing Orthogonal Matching Pursuit code in Numpy, part 1"&gt;in&lt;/a&gt; &lt;a href="http://localhost:8001/2011/08/11/optimizing-orthogonal-matching-pursuit-code-in-numpy-part-2/" title="Optimizing Orthogonal Matching Pursuit code in Numpy, part 2"&gt;the&lt;/a&gt; &lt;a href="http://localhost:8001/2011/11/18/the-nasty-bug-crawling-in-my-orthogonal-matching-pursuit-code/" title="The nasty bug crawling in my Orthogonal Matching Pursuit code"&gt;past&lt;/a&gt; that
I can do algorithmic and memory layout optimizations in numerical code.
There are parts in the scikit-learn that can benefit from such work: for
example only recently Peter merged this &lt;a href="https://github.com/scikit-learn/scikit-learn/pull/545"&gt;pull request&lt;/a&gt; significantly
improving SGDClassifier&amp;#8217;s test time performance by switching the memory
layout of the coefficients: they were laid out optimally for the
training phase, not for the prediction&amp;nbsp;phase.&lt;/p&gt;
&lt;p&gt;There are certainly more opportunities for such speed improvements in
the scikit. Of course there is a lot of code that can&amp;#8217;t reasonably be
made any faster (I have a feeling that SGDClassifier is at the moment
such a case, but we can&amp;#8217;t know for sure without heavy profiling). But
generally there are many speed fixes that could weigh a lot: for
example, a &lt;a href="http://cython.org/"&gt;Cython&lt;/a&gt; implementation of&amp;nbsp;the &lt;code&gt;euclidean_distances&lt;/code&gt;
function that is able to use preallocated memory will improve the
performance of raw NearestNeighbours queries as well as of the KMeans
and hierarchical clustering&amp;nbsp;algorithms.&lt;/p&gt;
&lt;h2 id="how-will-we-be-able-to-tell-if-you-succeed"&gt;How will we be able to tell if you succeed?&lt;a class="headerlink" href="#how-will-we-be-able-to-tell-if-you-succeed" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A key part of the GSoC project is setting up a
&lt;abbr title="Continuous Integration"&gt;&lt;span class="caps"&gt;CI&lt;/span&gt;&lt;/abbr&gt;-style benchmark platform.
The point is to be able to track how the speed of certain operations
evolves in time. For such purposes, Wes McKinney developed the
&lt;a href="https://github.com/pydata/vbench"&gt;vbench&lt;/a&gt; project, introduced in &lt;a href="http://wesmckinney.com/blog/?p=373"&gt;this blog post&lt;/a&gt;. The goal is for
every scikit-learn module to have several such benchmarks, for
differently shaped and structured&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;Having such a benchmark suite available is the equivalent of a test
suite, in terms of performance. It makes developers be extra conscious
of the effect of their changes. It also makes it more fun to chase speed
improvements, thanks to the positive reinforcement it&amp;nbsp;gives.&lt;/p&gt;
&lt;p&gt;There are some static benchmarks comparing the performance of
scikit-learn algorithms with other well-known libraries in the
&lt;a href="http://scikit-learn.sourceforge.net/ml-benchmarks/"&gt;ml-benchmarks&lt;/a&gt; project. It would be very helpful to have such a
benchmark suite that automatically keeps&amp;nbsp;up-to-date.&lt;/p&gt;
&lt;h2 id="side-effects"&gt;Side effects&lt;a class="headerlink" href="#side-effects" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The cool thing about such a project is that it should raise the overall
quality of the scikit. The refactoring will lead to an increase in test
coverage, because the low-coverage modules are expected to be less
optimized as well. Also, the benchmarks will lead to well-backed
summaries in the documentation, such as &lt;a href="http://scikit-learn.org/dev/modules/clustering.html#overview-of-clustering-methods"&gt;the one recently added in the
clustering section&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since the scikit is reaching a state where many well-known algorithms
are available, the &lt;strong&gt;1.0&lt;/strong&gt; release is slowly approaching. My Google
Summer of Code project should bring the scikit significantly closer to
that&amp;nbsp;milestone.&lt;/p&gt;</content><category term="scikit-learn"/><category term="gsoc"/><category term="proposal"/><category term="scikit-learn"/></entry><entry><title>Romanian people and coffee</title><link href="//vene.ro/blog/romanian-people-and-coffee.html" rel="alternate"/><published>2012-04-13T21:20:00+02:00</published><updated>2012-04-13T21:20:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-04-13:/blog/romanian-people-and-coffee.html</id><summary type="html">&lt;p&gt;So I got my hands of the &lt;a href="http://googleresearch.blogspot.com/2006/08/all-our-n-gram-are-belong-to-you.html" title="Google Research"&gt;Google N-gram data&lt;/a&gt; for the Romanian
language. It&amp;#8217;s noisy as hell, has some other subtle issues too, but
here&amp;#8217;s the first thing I&amp;nbsp;noticed:&lt;/p&gt;
&lt;p&gt;The Romanian word for coffee is &lt;em&gt;cafea&lt;/em&gt;, and the more you crave it, the
longer you pronunce …&lt;/p&gt;</summary><content type="html">&lt;p&gt;So I got my hands of the &lt;a href="http://googleresearch.blogspot.com/2006/08/all-our-n-gram-are-belong-to-you.html" title="Google Research"&gt;Google N-gram data&lt;/a&gt; for the Romanian
language. It&amp;#8217;s noisy as hell, has some other subtle issues too, but
here&amp;#8217;s the first thing I&amp;nbsp;noticed:&lt;/p&gt;
&lt;p&gt;The Romanian word for coffee is &lt;em&gt;cafea&lt;/em&gt;, and the more you crave it, the
longer you pronunce the final &lt;em&gt;a&lt;/em&gt;: I really need some &lt;em&gt;cafeaaaa&lt;/em&gt; right&amp;nbsp;now.&lt;/p&gt;
&lt;p&gt;Thanks to Google, here are the&amp;nbsp;numbers:&lt;/p&gt;
&lt;p&gt;[![Distribution of the length of the final letter in the Romanian word
for&amp;nbsp;coffee.][]][]&lt;/p&gt;
&lt;p&gt;Post scriptum: I hope you like the theme: I installed the &lt;a href="http://www.huyng.com/posts/sane-color-scheme-for-matplotlib/" title="www.huyng.com/posts/sane-color-scheme-for-matplotlib"&gt;sane
matplotlib color scheme&lt;/a&gt; from Huy&amp;nbsp;Nguyen.&lt;/p&gt;
&lt;p&gt;[![Distribution of the length of the final letter in the Romanian word
  for coffee.][]]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2012/04/cafeaaa.png&lt;/p&gt;</content><category term="nlp"/><category term="Uncategorized"/><category term="coffee"/><category term="ngram"/></entry><entry><title>Nash-Williams theorem on the Hamiltonian property of some regular graphs</title><link href="//vene.ro/blog/nash-williams-theorem-on-the-hamiltonian-property-of-some-regular-graphs.html" rel="alternate"/><published>2012-01-29T22:31:00+01:00</published><updated>2012-01-29T22:31:00+01:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-01-29:/blog/nash-williams-theorem-on-the-hamiltonian-property-of-some-regular-graphs.html</id><summary type="html">&lt;p&gt;I have been digging on the internet for the proof of this theorem for
the last couple of days without success. The result was published by Sir
Crispin Nash-Williams as &lt;em&gt;Valency Sequences which force graphs to have
Hamiltonian Circuits&lt;/em&gt;. Interim Rep, University of Waterloo Res Rep.,
Waterloo, Ontario, 1969. However …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been digging on the internet for the proof of this theorem for
the last couple of days without success. The result was published by Sir
Crispin Nash-Williams as &lt;em&gt;Valency Sequences which force graphs to have
Hamiltonian Circuits&lt;/em&gt;. Interim Rep, University of Waterloo Res Rep.,
Waterloo, Ontario, 1969. However, this old paper is unavailable online
but I have a proof in some lecture notes from my class, that I want to
share&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Theorem.&lt;/strong&gt; Let [latex]G=(V, E)[/latex] be an [latex]n[/latex]-regular
graph with [latex]|V| = 2n + 1[/latex]. Then, [latex]G[/latex] is&amp;nbsp;Hamiltonian.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Proof.&lt;/strong&gt; We first remark that [latex]n[/latex] must be even, since
$$\sum_{x \in V} d(x) = n(2n + 1) = 2|E|$$ We might try to apply
Dirac&amp;#8217;s theorem, which would give us a Hamiltonian cycle if [latex]
\forall x \in V, d(x) \geq \frac{|V|}{2}[/latex]. But in the current
case, [latex]\forall x \in V, d(x) = n \&amp;lt;&amp;nbsp;\frac{2n+1}{2}[/latex].&lt;/p&gt;
&lt;p&gt;So we force Dirac by adding an extra vertex [latex]w[/latex] and
connecting it to all [latex] x \in V [/latex]. In this new graph
[latex]G&amp;#8217;[/latex], [latex]d(x) = n + 1 \forall x \in V[/latex] and
[latex]d(w) = 2n + 1[/latex]. Therefore we have a Hamiltonian cycle that
passes through [latex]w[/latex] and in which, [latex]w[/latex] is
adjacent to two vertices [latex]x[/latex] and [latex]y \in V[/latex].
Therefore this cycle induces a Hamiltonian path in [latex]G[/latex]:
$$P = [x = v_0, v_1, &amp;#8230;, v_{2n-1}, v_{2n}=y]&amp;nbsp;$$&lt;/p&gt;
&lt;p&gt;Suppose that [latex]G[/latex] is not Hamiltonian. It follows that if
[latex] v_0v_i \in E [/latex], then [latex] v_{i-1}v_{2n} \notin
E[/latex] and also that if [latex] v_0v_i \notin E [/latex], then
[latex] v_{i-1}v_{2n} \in&amp;nbsp;E[/latex].&lt;/p&gt;
&lt;p&gt;We have two cases. If [latex]v_0[/latex] is adjacent to [latex]v_1,
&amp;#8230;, v_n[/latex] then it follows that [latex]v_{2n}[/latex] is
adjacent to [latex]v_n, v_{n+1}, &amp;#8230;, v_{2n-1}[/latex], since it
cannot be adjacent to any [latex]v_i, i \&amp;lt; n[/latex] without creating a
Hamiltonian cycle. But in this case, in the graph induced by the first
half [latex]G[\{v_0, v_1, &amp;#8230; v_n\}][/latex], [latex]v_n[/latex]
cannot be adjacent to all the others, since in [latex]G[/latex] it has
degree [latex]n[/latex] and it already has [latex]2[/latex] outgoing
edges. So there is at least one vertex [latex]v_i, i \&amp;lt; n[/latex] that
isn&amp;#8217;t adjacent to it, which means [latex]v_i[/latex] is adjacent to
some [latex]v_j, j &amp;gt; n[/latex], thus forming a Hamiltonian&amp;nbsp;cycle.&lt;/p&gt;
&lt;p&gt;In the second case, we have a vertex [latex]v_i, 2 \leq i \leq 2n -
1[/latex] such that [latex]v_0v_i \notin E[/latex] and
[latex]v_0v_{i+1} \in E[/latex]. This also means that
[latex]v_{i-1}v_{2n} \in&amp;nbsp;E[/latex].&lt;/p&gt;
&lt;p&gt;We therefore have a cycle of length [latex]2n[/latex] in
[latex]G[/latex] that excludes [latex]v_i[/latex]. Let&amp;#8217;s rename this
cycle [latex]C=[y_1, y_2, &amp;#8230;, y_{2n}, y_1][/latex] and&amp;nbsp;[latex]v_i=y_0[/latex].&lt;/p&gt;
&lt;p&gt;[latex]y_0[/latex] cannot be adjacent to two consecutive vertices
[latex]y_i[/latex] and [latex]y_{i+1}[/latex] because this will give a
Hamiltonian cycle. But we know that [latex]deg(y_0) = n[/latex]. It
follows that it&amp;#8217;s adjacent to all of the even or odd numbered vertices.
We assume the latter, without loss of generality. Let [latex]2k[/latex]
be some even index. Notice that we have [latex]\{y_0y_{2k-1},
y_0y_{2k+1}\} \subset E[/latex] and we can follow the cycle
[latex]C[/latex] from [latex]y_{2k+1}[/latex] all the way back to
[latex]y_{2n-1}[/latex] giving us a new cycle [latex]C&amp;#8217; = [y_1, y_2,
&amp;#8230;, y_{2n-1}, y_0, y_{2k+1}, &amp;#8230;, y_{2n}, y_1][/latex] also of
length [latex]2n[/latex]. So by repeating the same reasoning for every
even vertex, by placing it in the middle and building a cycle around it,
it follows that every even vertex is adjacent to all the odd vertices.
But there are [latex]n+1[/latex] even indices, so it follows that the
degree of any odd vertex is at least [latex]n+1[/latex], contradicting
the initial conditions of the theorem.&amp;nbsp;[latex]\square[/latex]&lt;/p&gt;</content><category term="graph theory"/><category term="graph"/><category term="graph theory"/><category term="hamiltonian"/><category term="nash-williams"/><category term="Uncategorized"/></entry><entry><title>Moving out</title><link href="//vene.ro/blog/moving-out.html" rel="alternate"/><published>2012-01-06T00:16:00+01:00</published><updated>2012-01-06T00:16:00+01:00</updated><author><name>vene</name></author><id>tag:vene.ro,2012-01-06:/blog/moving-out.html</id><summary type="html">&lt;p&gt;Happy new year,&amp;nbsp;friends!&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve made a New Year&amp;#8217;s resolution to build a better web presence and
make better use of the domain that I previously only used for&amp;nbsp;mail.&lt;/p&gt;
&lt;p&gt;This has prompted me to move my blog over to http://localhost:8001 which
hopefully is shorter, better …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Happy new year,&amp;nbsp;friends!&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve made a New Year&amp;#8217;s resolution to build a better web presence and
make better use of the domain that I previously only used for&amp;nbsp;mail.&lt;/p&gt;
&lt;p&gt;This has prompted me to move my blog over to http://localhost:8001 which
hopefully is shorter, better, faster and&amp;nbsp;stronger!&lt;/p&gt;</content><category term="Uncategorized"/><category term="Uncategorized"/></entry><entry><title>The nasty bug crawling in my Orthogonal Matching Pursuit code</title><link href="//vene.ro/blog/the-nasty-bug-crawling-in-my-orthogonal-matching-pursuit-code.html" rel="alternate"/><published>2011-11-18T20:51:00+01:00</published><updated>2011-11-18T20:51:00+01:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-11-18:/blog/the-nasty-bug-crawling-in-my-orthogonal-matching-pursuit-code.html</id><summary type="html">&lt;p&gt;A while back, Bob L. Sturm blogged about a &lt;a href="http://media.aau.dk/null_space_pursuits/2011/10/efficient-omp.html"&gt;similar implementation of
&lt;span class="caps"&gt;OMP&lt;/span&gt;&lt;/a&gt; to the one in scikit-learn. Instead of using the Cholesky
decomposition like we did, his Matlab code uses the &lt;span class="caps"&gt;QR&lt;/span&gt; decomposition, to
a similar (or maybe even identical) outcome, in theory. So lucky that
Alejandro pointed out …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A while back, Bob L. Sturm blogged about a &lt;a href="http://media.aau.dk/null_space_pursuits/2011/10/efficient-omp.html"&gt;similar implementation of
&lt;span class="caps"&gt;OMP&lt;/span&gt;&lt;/a&gt; to the one in scikit-learn. Instead of using the Cholesky
decomposition like we did, his Matlab code uses the &lt;span class="caps"&gt;QR&lt;/span&gt; decomposition, to
a similar (or maybe even identical) outcome, in theory. So lucky that
Alejandro pointed out to him the existence of the scikit-learn
implementation, and that Bob&amp;#8217;s code &lt;a href="http://media.aau.dk/null_space_pursuits/2011/10/omp-in-python-strange-results.html"&gt;exposed a bug&lt;/a&gt; that all the test
coverage didn&amp;#8217;t catch! This plot should increase, certainly not
decrease! Something is clearly wrong here.&lt;br /&gt;
&lt;a href="http://media.aau.dk/null_space_pursuits/2011/10/omp-in-python-strange-results.html"&gt;&lt;img alt="OMP buggy phase transition, decreasing instead of
increasing" src="http://media.aau.dk/null_space_pursuits/2011/10/17/OMPscikit.png" title="OMP buggy phase transition" /&gt;&lt;/a&gt;&lt;br /&gt;
Luckily we were able to find it and &lt;a href="http://media.aau.dk/null_space_pursuits/2011/10/to-the-rescue.html"&gt;fix it&lt;/a&gt; very quickly. I have
updated the old entries I wrote on the &lt;span class="caps"&gt;OMP&lt;/span&gt; optimizations, so they no
longer include the bug. But I take this opportunity to explain what
exactly went&amp;nbsp;wrong.&lt;/p&gt;
&lt;p&gt;A key part of the optimization was that slicing out arbitrary columns
out of an array is slow when they are passed to &lt;span class="caps"&gt;BLAS&lt;/span&gt; functions like
matrix multiplication. In order to make the most out of your code, the
data should have a contiguous layout. We achieved this by swapping
active dictionary atoms (columns) to the beginning of the&amp;nbsp;array.&lt;/p&gt;
&lt;p&gt;Something that can happen, but won&amp;#8217;t happen very often, is that after an
atom is selected as active, the atom that takes its place after swapping
needs to be selected. This is rare because dictionaries have many
columns, out of which only very very few will be active. But when it
happens, because the code didn&amp;#8217;t keep track of swapped indices, the
corresponding coefficient of the solution would get updated twice,
leading to more zero entries than we should have. A keen eye could have
noticed that the first `n_nonzero_coefs` entries in &lt;span class="caps"&gt;OMP&lt;/span&gt; solution
vectors were never non-zero. But alas, my eye was not a keen one at&amp;nbsp;all.&lt;/p&gt;
&lt;p&gt;In other words, the following test (that was written after the bug was
found, unfortunately) was failing:&lt;br /&gt;
[sourcecode lang=&amp;#8221;Python&amp;#8221;]&lt;br /&gt;
def test_swapped_regressors():&lt;br /&gt;
gamma = np.zeros(n_features)&lt;br /&gt;
# X[:, 21] should be selected first, then X[:, 0] selected second,&lt;br /&gt;
# which will take X[:, 21]&amp;#8217;s place in case the algorithm does&lt;br /&gt;
# column swapping for optimization (which is the case at the moment)&lt;br /&gt;
gamma[21] = 1.0&lt;br /&gt;
gamma[0] = 0.5&lt;br /&gt;
new_y = np.dot(X, gamma)&lt;br /&gt;
new_Xy = np.dot(X.T, new_y)&lt;br /&gt;
gamma_hat = orthogonal_mp(X, new_y, 2)&lt;br /&gt;
gamma_hat_gram = orthogonal_mp_gram(G, new_Xy, 2)&lt;br /&gt;
# active indices should be [0, 21], but prior to the bugfix&lt;br /&gt;
# the algorithm would update only [21] but twice&lt;br /&gt;
assert_equal(np.flatnonzero(gamma_hat), [0, 21])&lt;br /&gt;
assert_equal(np.flatnonzero(gamma_hat_gram), [0, 21])&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Note that this bug has been fixed for a while, but I didn&amp;#8217;t get the free
time to write this post until now. Good news is: we fixed it, and did so
very quickly after the report. So you can still trust me, I&amp;nbsp;guess!&lt;/p&gt;</content><category term="scikit-learn"/><category term="bug"/><category term="omp"/><category term="orthogonal matching pursuit"/><category term="dictionary learning"/><category term="scikit-learn"/></entry><entry><title>Sampling Gamma random variates through the ratio-of-uniforms method</title><link href="//vene.ro/blog/sampling-gamma-random-variates-through-the-ratio-of-uniforms-method.html" rel="alternate"/><published>2011-10-09T15:40:00+02:00</published><updated>2011-10-09T15:40:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-10-09:/blog/sampling-gamma-random-variates-through-the-ratio-of-uniforms-method.html</id><summary type="html">&lt;p&gt;One year ago I had the chance to take a class on Monte Carlo simulation
with prof. Ion Văduva, and my assignment for the class was to implement
exactly what it says in the title of the blog post. I am going to walk
you through the idea behind&amp;nbsp;this …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One year ago I had the chance to take a class on Monte Carlo simulation
with prof. Ion Văduva, and my assignment for the class was to implement
exactly what it says in the title of the blog post. I am going to walk
you through the idea behind&amp;nbsp;this.&lt;/p&gt;
&lt;h3 id="general-formulation"&gt;General formulation&lt;a class="headerlink" href="#general-formulation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The ratio-of-uniforms is a method that can be applied to many density
functions. Essentially, given a density function over [latex]
\mathbb{R}\^m[/latex], [latex] f(x) = \frac{h(x)}{H}[/latex] where
[latex]H[/latex] is a normalization constant (ie. [latex] h(x) \geq
0[/latex], [latex] H = \int h(x)dX[/latex]). Given a parameter [latex]
c &amp;gt; 0 [/latex] and a parametrization [latex]\phi[/latex] from [latex]
\mathbb{R}\^{m+1}[/latex] to [latex] \mathbb{R}\^{m}[/latex] expressed
as: $$ \phi(v_0, v_1, &amp;#8230;, v_m) = \left ( \frac{v_1}{v_0\^c},
\frac{v_2}{v_0\^c}, &amp;#8230;, \frac{v_m}{v_0\^c} \right )$$&lt;br /&gt;
Define the set [latex] \mathcal{C} = \{\mathbf{v} \big |
\gamma(\mathbf{v}) \leq 0, v_0 &amp;gt; 0\} \in
\mathbb{R}\^{m+1}[/latex] where&lt;br /&gt;
$$\gamma(\mathbf{v}) = \log v_0 - \frac{\log h(\phi(v_0,
v_1, &amp;#8230;, v_m))}{mc + 1}$$ If [latex] \mathcal{C}[/latex] is
bounded and we sample a uniform vector [latex] \mathbf{V} \sim
\text{Uniform}(\mathcal{C})[/latex] then [latex] \phi(\mathbf{V})
\sim f(x)[/latex]. Also note that the measure (volume) of the set
[latex] \mathcal{C}[/latex] is [latex] \frac{H}{mc + 1}[/latex]. I do
not have any references for the proof, except for a book in Romanian,
but if you are interested, just leave me a comment and I&amp;#8217;ll do a
follow-up post with the&amp;nbsp;proofs.&lt;/p&gt;
&lt;h3 id="univariate-scenario"&gt;Univariate scenario&lt;a class="headerlink" href="#univariate-scenario" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For the univariate case, all the above simplifies to $$ \mathcal{C} =
\left \{(u, v) \Big | 0 \&amp;lt; u \&amp;lt; \sqrt&lt;br /&gt;
{h\left (\frac{v}{u\^c}\right )} \right \} $$. We generate
[latex] (U, V) \sim \text{Uniform}(\mathcal{C})[/latex] and take
[latex] \frac{V}{U\^c} \sim f(x)[/latex].&lt;br /&gt;
Since we are looking at the (univariate) Gamma distribution, described
by: $$ f(x; \nu, \theta) = \frac{x\^{\mu - 1}
\exp(\frac{-x}{\theta})}{\theta\^k\Gamma(k)}$$ [latex]
\nu[/latex] is the shape parameter and [latex] \theta[/latex] is the
scale parameter.&lt;br /&gt;
But because of the property that if [latex] X \sim \text{Gamma}(\nu,
\theta)[/latex], then for any [latex] k &amp;gt; 0[/latex], [latex] kX \sim
\text{Gamma}(\nu, k\theta)[/latex], we conclude that we can fix
[latex] \theta[/latex] to 1 without loss of generality. Replacing in
the style of the definition in the previous section, we have [latex]
h(x; \nu) = x\^{\nu-1}e\^{-x}[/latex] and [latex] H_\nu =
\Gamma(\nu)[/latex].&lt;br /&gt;
This allows us to compute the equation of the boundary of the set
[latex] \mathcal{C}[/latex] which ends up being described by
[latex]\gamma(u, v) = \log{u} - \frac{\nu - 1}{c + 1}
\log{\left(\frac{v}{u\^c}\right)} + \frac{1}{c+1}
\frac{v}{u\^c}[/latex]. For visualisation purposes, here is how it
would look like for [latex] \nu=6, c=1[/latex] (plotted using &lt;a href="http://www.wolframalpha.com/" title="Wolfram Alpha"&gt;Wolfram
Alpha&lt;/a&gt;):[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/10/regiunea.png" title="The accepting set C" /&gt;]&lt;a href="http://localhost:8001/wp-content/uploads/2011/10/regiunea.png" title="The accepting set C"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="sampling-algorithm"&gt;Sampling algorithm&lt;a class="headerlink" href="#sampling-algorithm" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In order to uniformly sample from this set, we can apply basic rejection
sampling: just uniformly sample from a rectangular region surrounding
the set, and reject the points that do not satisfy the condition. In
order to do this as efficiently as possible, we need to compute the
minimal bounding box, which can be done by solving a couple of
optimization problems using Lagrange multipliers and the &lt;span class="caps"&gt;KKT&lt;/span&gt; conditions.
Also by looking closely at the image, you can see that the lower left
corner is exactly the origin: this turns out not to be a coincidence. I
won&amp;#8217;t go into detail here, but here are the bounds I derived:&lt;br /&gt;
$$ 0 \&amp;lt; u \&amp;lt; (\nu - 1)\^\frac{\nu - 1}{c + 1} e \^ {-\frac{\nu -
1}{c + 1}} \text{ and } 0\&amp;lt; v \&amp;lt; \left(\frac{c\nu +
1}{c}\right)\^{\frac{c\nu + 1}{c + 1}} e \^ {- \frac {c\nu +&amp;nbsp;1}{c+1}}$$&lt;/p&gt;
&lt;p&gt;The probability of acceptance (which can be seen as the efficiency) of
the rejection sampling method is given by the ratio of the areas of the
set [latex] \mathcal{C}[/latex] and the bounding box. The larger this
probability, the less points we throw away and the more efficient the
algorithm is. Using the values derived above, this probability is: $$
p(\nu, c) = \frac{\Gamma(\nu)e\^{\nu}}{(c+1) (\nu -
1)\^{\frac{\nu - 1}{c + 1}} \left(\frac{c\nu +
1}{c}\right)\^{\frac{c\nu + 1}{c +&amp;nbsp;1}}}$$&lt;/p&gt;
&lt;p&gt;Personally I got stumped here. The idea would be to determine the ideal
[latex] c[/latex] for a given [latex] \nu[/latex] in order to maximize
the probability, but I didn&amp;#8217;t manage to do it (I leave it as an exercise
for the reader ;)). Anyway, this is enough to proceed with an
implementation, so I&amp;#8217;m gonna give the Python code for it. Note that I
used the name k for the shape parameter instead of [latex] \nu[/latex].
Also note that the case when [latex] 0 \&amp;lt; \nu \&amp;lt; 1[/latex] needed to be
treated separately, which I did using the following property: Let
[latex] \nu \in (0, 1)[/latex]. If [latex] X&amp;#8217; \sim
\text{Gamma}(1+\nu, 1), U \sim \text{Uniform}(0, 1)[/latex] then
$$ X = X&amp;#8217; \cdot \sqrt[\nu]{U} \sim \text{Gamma}(\nu, 1)$$ For
a proof of this fact, see [&lt;a href="#footnote-1"&gt;1&lt;/a&gt;], which is a great article on
generating Gamma&amp;nbsp;variates.&lt;/p&gt;
&lt;h3 id="implementation"&gt;Implementation&lt;a class="headerlink" href="#implementation" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;[sourcecode language=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
from import numpy as&amp;nbsp;np&lt;/p&gt;
&lt;p&gt;def _cond(u, v, k, c):&lt;br /&gt;
&amp;#8220;&amp;#8221;&amp;#8220;Identity function describing the acceptance region&amp;#8221;&amp;#8220;&amp;#8221;&lt;br /&gt;
x = v / u ** c&lt;br /&gt;
return (c + 1) * np.log(u) \&amp;lt;= (k - 1) * np.log(x) -&amp;nbsp;x&lt;/p&gt;
&lt;p&gt;def vn_standard_gamma(k, c=1.0, rng=np.random):&lt;br /&gt;
&amp;#8220;&amp;#8221;&amp;#8220;Generates a single standard gamma random variate&amp;#8221;&amp;#8220;&amp;#8221;&lt;br /&gt;
if k \&amp;lt;= 0:&lt;br /&gt;
raise ValueError(&amp;#8220;Gamma shape should be positive&amp;#8221;)&lt;br /&gt;
elif k \&amp;lt; 1:&lt;br /&gt;
return vn_standard_gamma(1 + k, c, rng) * rng.uniform() ** (1 /
k)&lt;br /&gt;
elif k == 1:&lt;br /&gt;
return rng.standard_exponential()&lt;br /&gt;
else:&lt;br /&gt;
a, b = get_bounds(k, c)&lt;br /&gt;
while True:&lt;br /&gt;
u, v = rng.uniform(0, a), rng.uniform(0, b)&lt;br /&gt;
if _cond(u, v, k, c):&lt;br /&gt;
break;&lt;br /&gt;
return v / u **&amp;nbsp;c&lt;/p&gt;
&lt;p&gt;def vn_gamma(k, t, shape=1, c=1.0, rng=np.random):&lt;br /&gt;
&amp;#8220;&amp;#8221;&amp;#8220;Vectorized function to generate multiple gamma variates&amp;#8221;&amp;#8220;&amp;#8221;&lt;br /&gt;
generator = lambda x: t * vn_standard_gamma(k, c, rng)&lt;br /&gt;
generator = np.vectorize(generator)&lt;br /&gt;
return&amp;nbsp;generator(np.empty(shape))&lt;/p&gt;
&lt;p&gt;def get_bounds(k, c=1.0):&lt;br /&gt;
&amp;#8220;&amp;#8221;&amp;#8220;Computes the minimal upper bounds surrounding the acceptance
region&amp;#8221;&amp;#8220;&amp;#8221;&lt;br /&gt;
a = ((k - 1) / np.e) ** ((k - 1) / (c + 1))&lt;br /&gt;
b = ((c * k + 1) / (c * np.e)) ** ((c * k + 1) / (c + 1))&lt;br /&gt;
return a,&amp;nbsp;b&lt;/p&gt;
&lt;p&gt;def prob_acc(k, c=1.0):&lt;br /&gt;
&amp;#8220;&amp;#8221;&amp;#8220;Calculates the probability of acceptance for the given
parameters&amp;#8221;&amp;#8220;&amp;#8221;&lt;br /&gt;
from scipy.special import gamma&lt;br /&gt;
a, b = get_bounds(k, c)&lt;br /&gt;
return gamma(k) / ((c + 1) * a * b)&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;h3 id="results"&gt;Results&lt;a class="headerlink" href="#results" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;And of course I should show you that it works. Here are some histograms
for various values of [latex] \nu[/latex], with the theoretical density
plotted in dotted red, after sampling [latex] 10\^5[/latex] values. The
y-axis is the frequency (sorry for labeling in Romanian), and for the
red dotted line it can be interpreted as the theoretical probability.
You can clearly see the goodness of fit is&amp;nbsp;excellent.&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/10/hist1.png" title="Histogram for nu=6" /&gt;]&lt;a href="http://localhost:8001/wp-content/uploads/2011/10/regiunea.png" title="The accepting set C"&gt;&lt;/a&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/10/hist2.png" title="Histogram for nu=100" /&gt;]&lt;a href="http://localhost:8001/wp-content/uploads/2011/10/regiunea.png" title="The accepting set C"&gt;&lt;/a&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/10/hist3.png" title="Histogram for nu=0.66" /&gt;]&lt;a href="http://localhost:8001/wp-content/uploads/2011/10/regiunea.png" title="The accepting set C"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;span id="footnote-1"&gt;&lt;a href="#footnote-1"&gt;1&lt;/a&gt;&lt;/span&gt;: George Marsaglia and Wai Wan Tsang.
1998. &lt;a href="http://www.jstatsoft.org/v03/i03/paper"&gt;The Monty Python method for generating random variables&lt;/a&gt;. &lt;span class="caps"&gt;ACM&lt;/span&gt;
Trans. Math. Softw. 24, 3 (September 1998), 341-350.
&lt;a href="http://doi.acm.org/10.1145/292395.292453"&gt;&lt;span class="caps"&gt;DOI&lt;/span&gt;=10.1145/292395.292453&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/10/regiunea.png" title="The accepting set C" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/10/regiunea.png&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/10/hist1.png" title="Histogram for nu=6" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/10/hist1.png&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/10/hist2.png" title="Histogram for nu=100" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/10/hist2.png&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/10/hist3.png" title="Histogram for nu=0.66" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/10/hist3.png&lt;/p&gt;</content><category term="python"/><category term="monte carlo"/><category term="numpy"/><category term="random sampling"/><category term="ratio-of-uniforms"/><category term="scipy"/><category term="python"/></entry><entry><title>RANLP 2011 in Hissar, BG</title><link href="//vene.ro/blog/ranlp-2011-in-hissar-bg.html" rel="alternate"/><published>2011-09-20T14:17:00+02:00</published><updated>2011-09-20T14:17:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-09-20:/blog/ranlp-2011-in-hissar-bg.html</id><summary type="html">&lt;p&gt;Last week was marked by the international &lt;span class="caps"&gt;RANLP&lt;/span&gt; (Recent Advances in
Natural Language Processing) conference, taking place in a nice spa in
Hissar, Bulgaria. The excellent folks from the &lt;a href="http://clg.wlv.ac.uk/"&gt;computational
linguistics group&lt;/a&gt; at the University of Wolverhampton were behind it,
together with the &lt;a href="http://www.iict.bas.bg/EN/index.html"&gt;Institute of Information and Communication
Technologies&lt;/a&gt; from …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last week was marked by the international &lt;span class="caps"&gt;RANLP&lt;/span&gt; (Recent Advances in
Natural Language Processing) conference, taking place in a nice spa in
Hissar, Bulgaria. The excellent folks from the &lt;a href="http://clg.wlv.ac.uk/"&gt;computational
linguistics group&lt;/a&gt; at the University of Wolverhampton were behind it,
together with the &lt;a href="http://www.iict.bas.bg/EN/index.html"&gt;Institute of Information and Communication
Technologies&lt;/a&gt; from the Bulgarian Academy of Sciences.&lt;br /&gt;
[caption id=&amp;#8221;attachment_247&amp;#8221; align=&amp;#8221;alignleft&amp;#8221; width=&amp;#8221;225&amp;#8221; caption=&amp;#8221;
&amp;#8220;][&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/09/319030_10150303323328171_677848170_8151131_1046590975_n1.jpg?w=225" title="Warm spring in Hissar" /&gt;]&lt;a href="http://localhost:8001/wp-content/uploads/2011/09/319030_10150303323328171_677848170_8151131_1046590975_n1.jpg?w=225" title="Warm spring in Hissar"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A fountain with warm mineral spring water in Hissarya. &lt;br /&gt;
&lt;small&gt;Picture by &lt;a href="http://pers-www.wlv.ac.uk/~ex0233/" title="Miranda Chong"&gt;Miranda&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;[/caption]&lt;br /&gt;
I must begin by thanking them: the organization was impeccable! I&amp;#8217;m not
sure, but I think that at one point Ivelina was even running around
buying routers to improve wifi coverage (which is already spectacular in
Bulgaria &amp;#8212; I&amp;#8217;ve received reports from &lt;a href="http://pers-www.wlv.ac.uk/~ex0233/" title="Miranda Chong"&gt;Miranda&lt;/a&gt; that you can get wifi
in the&amp;nbsp;mountains!)&lt;/p&gt;
&lt;p&gt;The schedule was busy, with three tracks going in parallel, in order to
cover a wide range of topics in computational linguistics. The student
workshop should also be noted for the excellent quality of the works&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;Of course it would be infeasible to write about all the great people I
met and their high quality work. And if I were to write about all the
fun we had, it would probably make this post look unprofessional :).
This doesn&amp;#8217;t mean I forgot about any of you, and as soon as I get the
chance to work on something related, I will most certainly write about
it, and&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;So, if I would have to summarize the trends and the ideas stated during
the conference and especially during the keynotes, I would&amp;nbsp;say:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When talking about word sense disambiguation, it&amp;#8217;s wrong to speak
    about the different meanings of a word, but rather about the
    potential a word has for bringing a certain meaning in a certain
    context. See &lt;a href="http://www.patrickhanks.com/"&gt;Patrick Hanks&lt;/a&gt;&amp;#8216; &lt;a href="http://nlp.fi.muni.cz/projects/cpa/"&gt;Corpus Pattern Analysis&lt;/a&gt;. Without
    something like this, to have good &lt;span class="caps"&gt;WSD&lt;/span&gt; you need to heavily adjust the
    overlapping meanings from a Wordnet-style&amp;nbsp;ontology.&lt;/li&gt;
&lt;li&gt;Certain relations, such as temporal and spacial ones, can naturally
    be modeled by complex domain-specific logics (see &lt;a href="http://sites.google.com/site/inderjeetmani/"&gt;Inderjeet
    Mani&lt;/a&gt;&amp;#8216;s new book, Interpreting Motion: Grounded Representations
    for Spatial Language, that is due for publishing). But these only
    appear in a small subset of human communication. The attempt to map
    human language to a complete logic in which to do general-purpose
    inference seems futile: &lt;a href="http://u.cs.biu.ac.il/~dagan/"&gt;Ido Dagan&lt;/a&gt; suggests textual entailment:
    reasoning directly in natural language, and only abstracting away to
    a formal logic system when need&amp;nbsp;arises.&lt;/li&gt;
&lt;li&gt;If you have a large enough sample of n-gram frequency data, you can
    eventually beat the performance you can get with a limited amount of
    labeled data, and most importantly: it generalizes much better when
    going out of the domain you trained on. Apparently the best tool for
    this at the moment is the &lt;a href="http://ngrams.googlelabs.com/datasets"&gt;Google n-gram data&lt;/a&gt;, which has some
    limitations. In time, we can easily extend this data by huge amounts
    by mining n-grams from Wikipedia (which allegedly has a higher count
    of distinct n-grams than the Google dataset), and more importantly,
    by aligning multi-language data, making use of transliterations and
    cognate&amp;nbsp;identification.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please note that I may be (or most probably, am) ignorant of older
instances of similar ideas, and I may have misunderstood certain claims.
Please feel free to discuss in the comments whether you think I forgot
about something important, or whether I am plain wrong about something.
In particular, I seem to have been completely ignorant of the existence
of the Google n-gram data, which has been around for quite a while, so I
must have missed other important things as&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;Take care, kind readers, and express your opinion!&lt;br /&gt;&amp;nbsp;V&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://localhost:8001/wp-content/uploads/2011/09/319030_10150303323328171_677848170_8151131_1046590975_n1.jpg?w=225" title="Warm spring in Hissar" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/09/319030_10150303323328171_677848170_8151131_1046590975_n1.jpg&lt;/p&gt;</content><category term="conferences"/><category term="conferences"/><category term="nlp"/></entry><entry><title>Dictionary learning in scikit-learn 0.9</title><link href="//vene.ro/blog/dictionary-learning-in-scikit-learn-0-9.html" rel="alternate"/><published>2011-09-19T19:15:00+02:00</published><updated>2011-09-19T19:15:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-09-19:/blog/dictionary-learning-in-scikit-learn-0-9.html</id><summary type="html">&lt;p&gt;Thanks to Olivier, Gaël and Alex, who reviewed the code heavily the last
couple of days, and with apologies for my lack of activity during a
sequence of conferences, Dictionary learning has officially been merged
into scikit-learn master, and just in time for the new scikit-learn 0.9
release. Here …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Thanks to Olivier, Gaël and Alex, who reviewed the code heavily the last
couple of days, and with apologies for my lack of activity during a
sequence of conferences, Dictionary learning has officially been merged
into scikit-learn master, and just in time for the new scikit-learn 0.9
release. Here are some glimpses of the examples you can run for&amp;nbsp;yourself:&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Dictionary learned from Lena patches" src="http://localhost:8001/wp-content/uploads/2011/09/plot_image_denoising_1.png" title="plot_image_denoising_1" /&gt;][]&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Noisy image for denoising" src="http://localhost:8001/wp-content/uploads/2011/09/plot_image_denoising_24.png" title="plot_image_denoising_24" /&gt;][]&lt;/p&gt;
&lt;p&gt;[![Image denoising with Dictionary learning and Orthogonal matching&amp;nbsp;pursuit][]][]&lt;/p&gt;
&lt;p&gt;The stars of this new release are: the manifold learning module by Jake
Vanderplas and Fabian Pedregosa, the Dirichlet process gaussian mixture
model by Alexandre Passos, and many others, as you can see from the
&lt;a href="http://scikit-learn.sourceforge.net/dev/whats_new.html" title="scikit-learn development changelog"&gt;development changelog&lt;/a&gt; (as soon as the release is made, I will update
this post with permanent&amp;nbsp;links).&lt;/p&gt;
&lt;p&gt;The release is due tomorrow. I will also be in charge with building the
Windows installers for this release, let&amp;#8217;s hope I do a good job and you
can think of me and smile when&amp;nbsp;installing!&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Dictionary learned from Lena patches" src="http://localhost:8001/wp-content/uploads/2011/09/plot_image_denoising_1.png" title="plot_image_denoising_1" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/09/plot_image_denoising_1.png&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Noisy image for denoising" src="http://localhost:8001/wp-content/uploads/2011/09/plot_image_denoising_24.png" title="plot_image_denoising_24" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/09/plot_image_denoising_24.png&lt;/p&gt;
&lt;p&gt;[![Image denoising with Dictionary learning and Orthogonal matching
  pursuit][]]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/09/plot_image_denoising_44.png&lt;/p&gt;</content><category term="scikit-learn"/><category term="dictionary learning"/><category term="scikit-learn"/></entry><entry><title>Long overdue update. EuroScipy and SSLST 2011</title><link href="//vene.ro/blog/long-overdue-update-euroscipy-and-sslst-2011.html" rel="alternate"/><published>2011-09-05T00:08:00+02:00</published><updated>2011-09-05T00:08:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-09-05:/blog/long-overdue-update-euroscipy-and-sslst-2011.html</id><summary type="html">&lt;p&gt;Anybody reading my blog should have expected me to blog about the end of
my GSoC. Sorry to disappoint, but I simply did not experience anything
similar to an ending. On the contrary, I feel like things have barely
started. Also, I apologize for one of the few posts here …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Anybody reading my blog should have expected me to blog about the end of
my GSoC. Sorry to disappoint, but I simply did not experience anything
similar to an ending. On the contrary, I feel like things have barely
started. Also, I apologize for one of the few posts here without pretty
pictures!&amp;nbsp;:)&lt;/p&gt;
&lt;p&gt;For the last two weeks, I&amp;#8217;ve been traveling. I attended the EuroScipy
conference thanks to Fabian, who offered me a place to sleep during the
week. We sprinted hard, we discussed tricky APIs, we drank a lot of
coffee, beer, and ate well in lovely Paris. It was great to meet all of
the celebrities, the people who keep the scientific Python globe&amp;nbsp;turning.&lt;/p&gt;
&lt;p&gt;Many thanks to Gael and Emmanuelle, who worked very, very hard on
organizing everything, so they weren&amp;#8217;t around and I didn&amp;#8217;t get to say
goodbye when I ran to catch my plane last&amp;nbsp;Sunday.&lt;/p&gt;
&lt;p&gt;I was in a hurry, heading to Tarragona, a beautiful city on the Catalan
coast, where the public university organized the 2011 summer school in
linguistics and speech technologies (&lt;span class="caps"&gt;SSLST&lt;/span&gt;). This was a great
opportunity to meet many fellow young researchers working in
computational linguistics. I will not go into details now, because I
plan expand on this, but I would like to state a couple of things.
Firsty, even though &lt;span class="caps"&gt;NLP&lt;/span&gt; seems to be mostly a Java-dominated affair (note
for example &lt;a href="http://nlp.stanford.edu/software/index.shtml"&gt;Stanford&amp;#8217;s &lt;span class="caps"&gt;NLP&lt;/span&gt; toolkit&lt;/a&gt; and &lt;a href="http://gate.ac.uk/"&gt;Sheffield&amp;#8217;s &lt;span class="caps"&gt;GATE&lt;/span&gt;&lt;/a&gt;), the
[computational linguistics and psycholinguistics research center
(CLiPS)][] at the University of Antwerp actually briefly manifested its
devotion to Python and &lt;span class="caps"&gt;NLTK&lt;/span&gt; via its research director, Walter&amp;nbsp;Daelemans.&lt;/p&gt;
&lt;p&gt;It was good to see a little love for Python in this field. &lt;span class="caps"&gt;NLTK&lt;/span&gt; is very
underrepresented in the SciPy community, I couldn&amp;#8217;t find anybody at the
EuroScipy conference knowing too much about it or about the people
behind&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Another lab that has done a lot of cool work is &lt;a href="http://www.uclouvain.be/cental"&gt;Cental&lt;/a&gt; at the
Catholic University of Louvain, and they also use Python for natural
language processing. Maybe in the coming years, we will see a Python for
Computational Linguistics sattelite, along with Physics and
Neuroscience. Doesn&amp;#8217;t it sound more fun?&amp;nbsp;:P&lt;/p&gt;
&lt;p&gt;Secondly, I wish &lt;span class="caps"&gt;SSLST&lt;/span&gt; were organized by someone like Gael! As the
discussion at dinner regarding who will organize next year&amp;#8217;s EuroScipy
went, it is imperative that the organizers be actively involved in the
community, and generally passionate about it. Even though I&amp;#8217;m comparing
apples and oranges, Carlos Martin-Vide behaved in this context like a
old, tired, emotionless academic, not taking into account even lunch
breaks for the whole group, not to mention any sort of getting together
or even a group photo (which, alas, we were not able to take, apart from
small groups.) They said it couldn&amp;#8217;t be done. Of course it could, they
just didn&amp;#8217;t want it hard&amp;nbsp;enough.&lt;/p&gt;
&lt;p&gt;Finally, before signing off, I would like to announce that because the
Romanian Ministry of Education failed to specify the allocated number of
public positions for masters&amp;#8217; programmes, the admission exam at the
University of Bucharest will be delayed by a couple of weeks. Luckily,
this will allow me to attend &lt;a href="http://lml.bas.bg/ranlp2011/start3.php"&gt;&lt;span class="caps"&gt;RANLP&lt;/span&gt; 2011&lt;/a&gt; in Hissar, Bulgaria a week
from now, where I will present my poster entitled:&lt;br /&gt;
&amp;#8220;Can alternations be learned? A machine learning approach to Romanian
verb conjugation&amp;#8221; by Liviu P. Dinu, Emil Ionescu, Vlad Niculae and
Octavia-Maria Sulea. See you in&amp;nbsp;Hissar!&lt;/p&gt;</content><category term="conferences"/><category term="Uncategorized"/></entry><entry><title>Optimizing Orthogonal Matching Pursuit code in Numpy, part 2</title><link href="//vene.ro/blog/optimizing-orthogonal-matching-pursuit-code-in-numpy-part-2.html" rel="alternate"/><published>2011-08-11T19:39:00+02:00</published><updated>2011-08-11T19:39:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-08-11:/blog/optimizing-orthogonal-matching-pursuit-code-in-numpy-part-2.html</id><summary type="html">&lt;p&gt;&lt;span class="caps"&gt;EDIT&lt;/span&gt;: There was a bug in the final version of the code presented here.
It is fixed now, for its backstory, check out &lt;a href="http://venefrombucharest.wordpress.com/2011/11/18/the-nasty-bug-crawling-in-my-orthogonal-matching-pursuit-code/" title="The nasty bug crawling in my Orthogonal Matching Pursuit code"&gt;my blog post on it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When we last saw our hero, he was fighting with the dreaded
implementation of least-angle regression, knowing full well that it was …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;span class="caps"&gt;EDIT&lt;/span&gt;: There was a bug in the final version of the code presented here.
It is fixed now, for its backstory, check out &lt;a href="http://venefrombucharest.wordpress.com/2011/11/18/the-nasty-bug-crawling-in-my-orthogonal-matching-pursuit-code/" title="The nasty bug crawling in my Orthogonal Matching Pursuit code"&gt;my blog post on it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When we last saw our hero, he was fighting with the dreaded
implementation of least-angle regression, knowing full well that it was
his destiny to be&amp;nbsp;faster.&lt;/p&gt;
&lt;p&gt;We had come up with a more robust implementation, catching malformed
cases that would have broken the naive implementation, and also it was
orders of magnitude faster than said implementation. However, our
benchmark [&lt;a href="#footnote-1"&gt;1&lt;/a&gt;] showed that it was a couple of times slower than
least-angle&amp;nbsp;regression.&lt;/p&gt;
&lt;p&gt;By poking around&amp;nbsp;the &lt;code&gt;scikits.learn&lt;/code&gt; codebase, I noticed that there is a
triangular system solver&amp;nbsp;in &lt;code&gt;scikits.learn.utils.arrayfuncs&lt;/code&gt;. Unlike&amp;nbsp;the
&lt;code&gt;scipy.linalg&lt;/code&gt; one, this one only works with lower triangular arrays,
and it forcefully&amp;nbsp;overwrites &lt;code&gt;b&lt;/code&gt;. Even though if weren&amp;#8217;t faster, it
should still be&amp;nbsp;used: &lt;code&gt;scikits.learn&lt;/code&gt; aims to be as backwards-compatible
with SciPy as possible,&amp;nbsp;and &lt;code&gt;linalg.solve_triangular&lt;/code&gt; was added in
0.9.0. Anyway, let&amp;#8217;s just see whether it&amp;#8217;s&amp;nbsp;faster:&lt;/p&gt;
&lt;p&gt;[sourcecode language=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In &lt;a href="#footnote-1"&gt;1&lt;/a&gt;: import numpy as&amp;nbsp;np&lt;/p&gt;
&lt;p&gt;In &lt;a href="#footnote-2"&gt;2&lt;/a&gt;: from scipy import&amp;nbsp;linalg&lt;/p&gt;
&lt;p&gt;In [3]: from scikits.learn.datasets import&amp;nbsp;make_spd_matrix&lt;/p&gt;
&lt;p&gt;In [4]: from scikits.learn.utils.arrayfuncs import&amp;nbsp;solve_triangular&lt;/p&gt;
&lt;p&gt;In [5]: G =&amp;nbsp;make_spd_matrix(1000)&lt;/p&gt;
&lt;p&gt;In [6]: L = linalg.cholesky(G,&amp;nbsp;lower=True)&lt;/p&gt;
&lt;p&gt;In [7]: x =&amp;nbsp;np.random.randn(1000)&lt;/p&gt;
&lt;p&gt;In [8]: y =&amp;nbsp;x.copy()&lt;/p&gt;
&lt;p&gt;In [9]: timeit solve_triangular(L, x)&lt;br /&gt;
100 loops, best of 3: 3.45 ms per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [10]: timeit linalg.solve_triangular(L, y, lower=True,
overwrite_b=True)&lt;br /&gt;
10 loops, best of 3: 134 ms per loop&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Wow! That&amp;#8217;s 40x faster. We&amp;#8217;re catching two rabbits with one stone here,
let&amp;#8217;s do the change! Notice that we can just copy [latex]
\mathbf{v}[/latex] into the appropriate place in [latex] L[/latex] and
then solve in&amp;nbsp;place.&lt;/p&gt;
&lt;p&gt;But whoops! When solving the [latex] &lt;span class="caps"&gt;LL&lt;/span&gt;&amp;#8217;[/latex] system, we take
advantage of&amp;nbsp;the &lt;code&gt;transpose&lt;/code&gt; attribute&amp;nbsp;in &lt;code&gt;linalg.solve_triangular&lt;/code&gt;,
which&amp;nbsp;the &lt;code&gt;scikits.learn&lt;/code&gt; version does not expose. We could think of a
solution, but here&amp;#8217;s a better idea: Shouldn&amp;#8217;t there be some way to
directly solve the entire system in one&amp;nbsp;go?&lt;/p&gt;
&lt;p&gt;Well, there is. It is an &lt;span class="caps"&gt;LAPACK&lt;/span&gt; function by the name&amp;nbsp;of &lt;code&gt;potrs&lt;/code&gt;. If you
are not aware, &lt;span class="caps"&gt;LAPACK&lt;/span&gt; is a Fortran library with solvers for various
types of linear systems and eigenproblems. &lt;span class="caps"&gt;LAPACK&lt;/span&gt; along with &lt;span class="caps"&gt;BLAS&lt;/span&gt; (on
which it is based) pretty much powers all the scientific computation
that happens. &lt;span class="caps"&gt;BLAS&lt;/span&gt; is an &lt;span class="caps"&gt;API&lt;/span&gt; with multiple implementations dating from
1979, while &lt;span class="caps"&gt;LAPACK&lt;/span&gt; dates from 1992. If you ever used Matlab, this is
what was called behind the scenes. SciPy, again, provides a high-level
wrapper around this,&amp;nbsp;the &lt;code&gt;linalg.cho_solve&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;But SciPy also gives us the possibility to import functions directly
from &lt;span class="caps"&gt;LAPACK&lt;/span&gt;, through the use&amp;nbsp;of &lt;code&gt;linalg.lapack.get_lapack_funcs&lt;/code&gt;. Let&amp;#8217;s
see how the low-level &lt;span class="caps"&gt;LAPACK&lt;/span&gt; function compares to the SciPy wrapper, for
our use&amp;nbsp;case:&lt;/p&gt;
&lt;p&gt;[sourcecode language=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [11]: x =&amp;nbsp;np.random.randn(1000)&lt;/p&gt;
&lt;p&gt;In [12]: y =&amp;nbsp;x.copy()&lt;/p&gt;
&lt;p&gt;In [13]: timeit linalg.cho_solve((L, True), x)&lt;br /&gt;
1 loops, best of 3: 95.4 ms per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [14]: potrs, = linalg.lapack.get_lapack_funcs((&amp;#8216;potrs&amp;#8217;,),&amp;nbsp;(G,))&lt;/p&gt;
&lt;p&gt;In [15]: potrs&lt;br /&gt;
Out[15]: &amp;lt;fortran&amp;nbsp;object&amp;gt;&lt;/p&gt;
&lt;p&gt;In [16]: timeit potrs(L, y)&lt;br /&gt;
100 loops, best of 3: 9.49 ms per loop&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s 10 times faster! So now we found an obvious way to optimize the&amp;nbsp;code:&lt;/p&gt;
&lt;p&gt;[sourcecode language=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
def cholesky_omp(X, y, n_nonzero_coefs, eps=None):&lt;br /&gt;
min_float = np.finfo(X.dtype).eps&lt;br /&gt;
potrs, = get_lapack_funcs((&amp;#8216;potrs&amp;#8217;,), (X,))&lt;br /&gt;
alpha = np.dot(X.T, y)&lt;br /&gt;
residual = y&lt;br /&gt;
n_active = 0&lt;br /&gt;
idx =&amp;nbsp;[]&lt;/p&gt;
&lt;p&gt;max_features = X.shape&lt;a href="#footnote-1"&gt;1&lt;/a&gt; if eps is not None else n_nonzero_coefs&lt;br /&gt;
L = np.empty((max_features, max_features), dtype=X.dtype)&lt;br /&gt;
L[0, 0] =&amp;nbsp;1.&lt;/p&gt;
&lt;p&gt;while 1:&lt;br /&gt;
lam = np.abs(np.dot(X.T, residual)).argmax()&lt;br /&gt;
if lam &amp;lt; n_active or alpha[lam] ** 2 &amp;lt; min_float:&lt;br /&gt;
# atom already selected or inner product too small&lt;br /&gt;
warn(&amp;#8220;Stopping early&amp;#8221;)&lt;br /&gt;
break&lt;br /&gt;
if n_active &amp;gt; 0:&lt;br /&gt;
# Updates the Cholesky decomposition of X&amp;#8217; X&lt;br /&gt;
L[n_active, :n_active] = np.dot(X[:, idx].T, X[:, lam]&lt;br /&gt;
solve_triangular(L[:n_active, :n_active], L[n_active, :n_active])&lt;br /&gt;
d = np.dot(L[n_active, :n_active].T, L[n_active, :n_active])&lt;br /&gt;
if 1 - d &amp;lt;= min_float: # selected atoms are dependent&lt;br /&gt;
warn(&amp;#8220;Stopping early&amp;#8221;)&lt;br /&gt;
break&lt;br /&gt;
L[n_active, n_active] = np.sqrt(1 - d)&lt;br /&gt;
idx.append(lam)&lt;br /&gt;
# solve &lt;span class="caps"&gt;LL&lt;/span&gt;&amp;#8217;x = y in two steps:&lt;br /&gt;
gamma, _ = potrs(L[:n_active, :n_active], alpha[idx], lower=True,&lt;br /&gt;
overwrite_b=False)&lt;br /&gt;
residual = y - np.dot(X[:, idx], gamma)&lt;br /&gt;
if eps is not None and np.dot(residual.T, residual) &amp;lt;= eps:&lt;br /&gt;
break&lt;br /&gt;
elif n_active == max_features:&lt;br /&gt;
break&lt;br /&gt;
return gamma, idx&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Woohoo! But we still lag behind. Now that we delegated the trickiest
parts of the code to fast and reliable solvers, it&amp;#8217;s time to use a
profiler and see what the bottleneck is now. Python has excellent tools
for this purpose. What solved the problem in this case&amp;nbsp;was
&lt;code&gt;line_profiler&lt;/code&gt; [&lt;a href="#footnote-2"&gt;2&lt;/a&gt;]. There is a great article by Olivier Grisel here
&lt;a href="#footnote-2"&gt;2&lt;/a&gt; regarding how to use these profilers. I&amp;#8217;m just going to say&amp;nbsp;that
&lt;code&gt;line_profiler&lt;/code&gt;&lt;span class="quo"&gt;&amp;#8216;&lt;/span&gt;s output is very helpful, basically printing the time
taken by each line of code next to that&amp;nbsp;line.&lt;/p&gt;
&lt;p&gt;Running the profiler on this code, we found that 58% of the time is
spent on line 14, 20.5% on line 21, and 20.5% on line 32, with the rest
being insignificant&amp;nbsp;(&lt;code&gt;potrs&lt;/code&gt; takes 0.1%!). The code is clearly dominated
by the matrix multiplications. By running some more timings with
IPython, I found that multiplying such column-wise views of the data&amp;nbsp;as
&lt;code&gt;X[:, idx]&lt;/code&gt; is considerably slower then multiplying a contiguous array.
The least-angle regression code&amp;nbsp;in &lt;code&gt;scikits.learn&lt;/code&gt; avoids this by
swapping columns towards the front of the array as they are chosen, so
we can&amp;nbsp;replace &lt;code&gt;X[:, idx]&lt;/code&gt; with &lt;code&gt;X[:, :n_active]&lt;/code&gt;. The nice part is that
if the array is stored in Fortran-contiguous order (ie. column
contiguous order, as opposed to row contiguous order, as in C), swapping
two columns is a very fast operation!. Let&amp;#8217;s see some more&amp;nbsp;benchmarks!&lt;/p&gt;
&lt;p&gt;[sourcecode language=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [17]: X = np.random.randn(5000,&amp;nbsp;5000)&lt;/p&gt;
&lt;p&gt;In [18]: Y = X.copy(&amp;#8216;F&amp;#8217;) #&amp;nbsp;fortran-ordered&lt;/p&gt;
&lt;p&gt;In [19]: a, b = 1000,&amp;nbsp;2500&lt;/p&gt;
&lt;p&gt;In [20]: swap, = linalg.get_blas_funcs((&amp;#8216;swap&amp;#8217;,),&amp;nbsp;(X,))&lt;/p&gt;
&lt;p&gt;In [21]: timeit X[:, a], X[:, b] = swap(X[:, a], X[:, b])&lt;br /&gt;
100 loops, best of 3: 6.29 ms per&amp;nbsp;loop&lt;/p&gt;
&lt;p&gt;In [22]: timeit Y[:, a], Y[:, b] = swap(Y[:, a], Y[:, b])&lt;br /&gt;
10000 loops, best of 3: 111 us per loop&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;We can see that using Fortran-order takes us from the order of
miliseconds to the order of&amp;nbsp;microseconds!&lt;/p&gt;
&lt;p&gt;Side note: I almost fell into the trap of swapping columns the pythonic
way. That doesn&amp;#8217;t work:&lt;br /&gt;
[sourcecode language=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
In [23]: X[:, a], X[:, b] = X[:, b], X[:,&amp;nbsp;a]&lt;/p&gt;
&lt;p&gt;In [24]: np.testing.assert_array_equal(X[:, a], X[:,&amp;nbsp;b])&lt;/p&gt;
&lt;p&gt;In [25]:&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;However this trick works great for swapping elements of one-dimensional&amp;nbsp;arrays.&lt;/p&gt;
&lt;p&gt;Another small optimization that we can do: I found that on my system,
it&amp;#8217;s slightly faster to compute the norm using the &lt;span class="caps"&gt;BLAS&lt;/span&gt;&amp;nbsp;function &lt;code&gt;nrm2&lt;/code&gt;.
So by putting all of these together, we end up with the final version of
our&amp;nbsp;code:&lt;/p&gt;
&lt;p&gt;[sourcecode language=&amp;#8221;python&amp;#8221;]&lt;br /&gt;
def cholesky_omp(X, y, n_nonzero_coefs, eps=None,
overwrite_X=False):&lt;br /&gt;
if not overwrite_X:&lt;br /&gt;
X = X.copy(&amp;#8216;F&amp;#8217;)&lt;br /&gt;
else: # even if we are allowed to overwrite, still copy it if bad
order&lt;br /&gt;
X =&amp;nbsp;np.asfortranarray(X)&lt;/p&gt;
&lt;p&gt;min_float = np.finfo(X.dtype).eps&lt;br /&gt;
nrm2, swap = linalg.get_blas_funcs((&amp;#8216;nrm2&amp;#8217;, &amp;#8216;swap&amp;#8217;), (X,))&lt;br /&gt;
potrs, = get_lapack_funcs((&amp;#8216;potrs&amp;#8217;,),&amp;nbsp;(X,))&lt;/p&gt;
&lt;p&gt;indices = range(len(Gram)) # keeping track of swapping&lt;br /&gt;
alpha = np.dot(X.T, y)&lt;br /&gt;
residual = y&lt;br /&gt;
n_active =&amp;nbsp;0&lt;/p&gt;
&lt;p&gt;max_features = X.shape&lt;a href="#footnote-1"&gt;1&lt;/a&gt; if eps is not None else n_nonzero_coefs&lt;br /&gt;
L = np.empty((max_features, max_features), dtype=X.dtype)&lt;br /&gt;
L[0, 0] =&amp;nbsp;1.&lt;/p&gt;
&lt;p&gt;while True:&lt;br /&gt;
lam = np.abs(np.dot(X.T, residual)).argmax()&lt;br /&gt;
if lam &amp;lt; n_active or alpha[lam] ** 2 &amp;lt; min_float:&lt;br /&gt;
# atom already selected or inner product too small&lt;br /&gt;
warn(&amp;#8220;Stopping early&amp;#8221;)&lt;br /&gt;
break&lt;br /&gt;
if n_active &amp;gt; 0:&lt;br /&gt;
# Updates the Cholesky decomposition of X&amp;#8217; X&lt;br /&gt;
L[n_active, :n_active] = np.dot(X[:, :n_active].T, X[:, lam])&lt;br /&gt;
solve_triangular(L[:n_active, :n_active], L[n_active, :n_active])&lt;br /&gt;
v = nrm2(L[n_active, :n_active]) ** 2&lt;br /&gt;
if 1 - v &amp;lt;= min_float: # selected atoms are dependent&lt;br /&gt;
warn(&amp;#8220;Stopping early&amp;#8221;)&lt;br /&gt;
break&lt;br /&gt;
L[n_active, n_active] = np.sqrt(1 - v)&lt;br /&gt;
X.T[n_active], X.T[lam] = swap(X.T[n_active], X.T[lam])&lt;br /&gt;
alpha[n_active], alpha[lam] = alpha[lam], alpha[n_active]&lt;br /&gt;
indices[n_active], indices[lam] = indices[lam], indices[n_active]&lt;br /&gt;
n_active += 1&lt;br /&gt;
# solves &lt;span class="caps"&gt;LL&lt;/span&gt;&amp;#8217;x = y as a composition of two triangular systems&lt;br /&gt;
gamma, _ = potrs(L[:n_active, :n_active], alpha[:n_active],
lower=True,&lt;br /&gt;&amp;nbsp;overwrite_b=False)&lt;/p&gt;
&lt;p&gt;residual = y - np.dot(X[:, :n_active], gamma)&lt;br /&gt;
if eps is not None and nrm2(residual) ** 2 &amp;lt;= eps:&lt;br /&gt;
break&lt;br /&gt;
elif n_active == max_features:&lt;br /&gt;&amp;nbsp;break&lt;/p&gt;
&lt;p&gt;return gamma, indices[:n_active]&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Now, the benchmark at [&lt;a href="#footnote-1"&gt;1&lt;/a&gt;] indicates victory over least-angle
regression! I hope you have enjoyed this short tour. See you next&amp;nbsp;time!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/scikit-learn/scikit-learn/blob/master/benchmarks/bench_plot_omp_lars.py" title="Orthogonal matching pursuit versus least-angle regression"&gt;&lt;span id="footnote-1"&gt;1&lt;/span&gt;&lt;/a&gt;[]&lt;br /&gt;
&lt;a href="http://scikit-learn.sourceforge.net/dev/developers/performance.html#profiling-python-code" title="Profiling Python code"&gt;&lt;span id="footnote-2"&gt;2&lt;/span&gt;&lt;/a&gt;[]&lt;/p&gt;</content><category term="scikit-learn"/><category term="blas"/><category term="efficient"/><category term="lapack"/><category term="numpy"/><category term="omp"/><category term="orthogonal matching pursuit"/><category term="potrs"/><category term="scipy"/><category term="dictionary learning"/><category term="python"/><category term="scikit-learn"/></entry><entry><title>Optimizing Orthogonal Matching Pursuit code in Numpy, part 1</title><link href="//vene.ro/blog/optimizing-orthogonal-matching-pursuit-code-in-numpy-part-1.html" rel="alternate"/><published>2011-08-07T20:50:00+02:00</published><updated>2011-08-07T20:50:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-08-07:/blog/optimizing-orthogonal-matching-pursuit-code-in-numpy-part-1.html</id><summary type="html">&lt;p&gt;After intense code optimization work, my implementation of &lt;span class="caps"&gt;OMP&lt;/span&gt; finally
beat least-angle regression! This was the primary issue discussed during
the pull request, so once performance was taken care of, the code was
ready for merge. Orthogonal matching pursuit is now available in
scikits.learn as a sparse linear regression …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After intense code optimization work, my implementation of &lt;span class="caps"&gt;OMP&lt;/span&gt; finally
beat least-angle regression! This was the primary issue discussed during
the pull request, so once performance was taken care of, the code was
ready for merge. Orthogonal matching pursuit is now available in
scikits.learn as a sparse linear regression model. &lt;span class="caps"&gt;OMP&lt;/span&gt; is a key building
block of the dictionary learning code that we are working on&amp;nbsp;merging.&lt;/p&gt;
&lt;p&gt;I will go through the process of developing this particular piece of
code as an example of code refining and iterative improvements, as well
as for the useful notes it will provide on optimizing numerical Python
code. In the first part we will see how the code got from pseudocode
state to a reasonably efficient code with smart memory allocation. In
the next part we will see how to make it blazing fast by leveraging
[&lt;a href="#footnote-1"&gt;1&lt;/a&gt;] lower level &lt;span class="caps"&gt;BLAS&lt;/span&gt; and &lt;span class="caps"&gt;LAPACK&lt;/span&gt; routines, and how to use profiling
to find hot&amp;nbsp;spots.&lt;/p&gt;
&lt;p&gt;As stated before, orthogonal matching pursuit is a greedy algorithm for
finding a sparse solution [latex] \gamma[/latex] to a linear regression
problem [latex] X\gamma = y[/latex]. Mathematically, it approximates
the solution of the optimization&amp;nbsp;problem:&lt;/p&gt;
&lt;p&gt;$$ \text{argmin} {\big|\big|} \gamma {\big|\big|} _0 \text{
subject to }{\big |\big|}y-X\gamma{\big|\big|}_2\^2 \leq
\epsilon $$&lt;br /&gt;
or (under a different parametrization):&lt;br /&gt;
$$\text{argmin} {\big |\big|}y - X\gamma{\big |\big|}_2\^2
\text{ subject to } {\big|\big|}\gamma{\big|\big|}_0 \leq
n_{\text{nonzero&amp;nbsp;coefs}}$$&lt;/p&gt;
&lt;p&gt;In the code samples in this post I will omit the docstrings, but I will
follow the notation in the formulas&amp;nbsp;above.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important note:&lt;/strong&gt; The regressors/dictionary atoms (the columns of
[latex] X[/latex]) are assumed to be normalized throughout this post (as
well as usually any discussion of &lt;span class="caps"&gt;OMP&lt;/span&gt;). We also assume the following
imports:&lt;br /&gt;
[sourcecode language=&amp;#8221;Python&amp;#8221;]&lt;br /&gt;
import numpy as np&lt;br /&gt;
from scipy import linalg&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Orthogonal matching pursuit is a very simple algorithm in pseudocode,
and as I stated before, it almost writes itself in Numpy. For this
reason, instead of stating the pseudocode here, I will start with how
naively implemented &lt;span class="caps"&gt;OMP&lt;/span&gt; looks like in&amp;nbsp;Python:&lt;/p&gt;
&lt;p&gt;[sourcecode language=&amp;#8221;Python&amp;#8221;]&lt;br /&gt;
def orthogonal_mp(X, y, n_nonzero_coefs, eps=None):&lt;br /&gt;
residual = y&lt;br /&gt;
idx = []&lt;br /&gt;
if eps == None:&lt;br /&gt;
stopping_condition = lambda: len(idx) == n_nonzero_coefs&lt;br /&gt;
else:&lt;br /&gt;
stopping_condition = lambda: np.inner(residual, residual) &amp;lt;= eps&lt;br /&gt;
while not stopping_condition():&lt;br /&gt;
lam = np.abs(np.dot(residual, X)).argmax()&lt;br /&gt;
idx.append(lam)&lt;br /&gt;
gamma, _, _, _ = linalg.lstsq(X[:, idx], y)&lt;br /&gt;
residual = y - np.dot(X[:, idx], gamma)&lt;br /&gt;
return gamma, idx&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Using lambda expressions as stopping conditions never looked like a
brilliant idea, but it seems to me like the most elegant way to specify
such a variable stopping condition. However, the biggest slowdown in
this is the need for solving a least squares problem at each iteration,
while least-angle regression is known to produce the entire
regularization path for the cost of a single least squares problem. We
will also see that this implementation is more vulnerable to numerical
stability&amp;nbsp;issues.&lt;/p&gt;
&lt;p&gt;In [&lt;a href="#footnote-2"&gt;2&lt;/a&gt;], Rubinstein et al. described the Cholesky-&lt;span class="caps"&gt;OMP&lt;/span&gt; algorithm, an
implementation of &lt;span class="caps"&gt;OMP&lt;/span&gt; that avoids solving a new least squares problem at
each iteration by keeping a Cholesky decomposition [latex] &lt;span class="caps"&gt;LL&lt;/span&gt;&amp;#8217;[/latex]
of the Gram matrix [latex] G =
X_{\text{idx}}&amp;#8217;X_{\text{idx}}[/latex]. Because [latex]
X_{\text{idx}}[/latex] grows by exactly one column at each iteration,
[latex] L[/latex] can be updated according to the following rule: Given
[latex] A = \begin{pmatrix} \tilde{A} &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; \mathbf{v}&amp;#8217; &amp;#92; \mathbf{v}
&lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; c \end{pmatrix}[/latex], and knowing the decomposition of [latex]
\tilde{A} = \tilde{L}\tilde{L}&amp;#8217;[/latex], the Cholesky decomposition
[latex] A = &lt;span class="caps"&gt;LL&lt;/span&gt;&amp;#8217;[/latex] is given by $$ L = \begin{pmatrix}\tilde{L}
&lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; \mathbf{0} &amp;#92; \mathbf{w}&amp;#8217; &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; \sqrt{c - \mathbf{w}&amp;#8217;\mathbf{w}}
\end{pmatrix}, \text{ where } \tilde{L}\mathbf{w} =&amp;nbsp;\mathbf{v}$$&lt;/p&gt;
&lt;p&gt;Even if you are unfamiliar with the mathematical properties of the
Cholesky decomposition, you can see from the construction detailed above
that [latex] L[/latex] is always going to be a lower triangular matrix
(it will only have null elements above the main diagonal). Actually, the
letter L stands for lower. We have therefore replaced the step where we
needed to solve the least-squares problem [latex]
X_{\text{idx}}\gamma = y[/latex] with two much simpler computations:
solving [latex] \tilde{L}\mathbf{w} = \mathbf{v}[/latex] and solving
[latex] &lt;span class="caps"&gt;LL&lt;/span&gt;&amp;#8217;\gamma = X_{\text{idx}}&amp;#8217;y[/latex]. Due to the [latex]
L[/latex]&amp;#8217;s structure, these are much quicker operations than a least
squares projection.&lt;br /&gt;
Here is the initial way I implemented&amp;nbsp;this:&lt;/p&gt;
&lt;p&gt;[sourcecode&amp;nbsp;language=&amp;#8221;Python&amp;#8221;]&lt;/p&gt;
&lt;p&gt;def cholesky_omp(X, y, n_nonzero_coefs, eps=None):&lt;br /&gt;
if eps == None:&lt;br /&gt;
stopping_condition = lambda: it == n_nonzero_coefs&lt;br /&gt;
else:&lt;br /&gt;
stopping_condition = lambda: np.inner(residual, residual) \&amp;lt;=&amp;nbsp;eps&lt;/p&gt;
&lt;p&gt;alpha = np.dot(X.T, y)&lt;br /&gt;
residual = y&lt;br /&gt;
idx = []&lt;br /&gt;
L =&amp;nbsp;np.ones((1,1))&lt;/p&gt;
&lt;p&gt;while not stopping_condition():&lt;br /&gt;
lam = np.abs(np.dot(residual, X)).argmax()&lt;br /&gt;
if len(idx) &amp;gt; 0:&lt;br /&gt;
w = linalg.solve_triangular(L, np.dot(X[:, idx].T, X[:, lam]),&lt;br /&gt;
lower=True)&lt;br /&gt;
L = np.r_[np.c_[L, np.zeros(len(L))],&lt;br /&gt;
np.atleast_2d(np.append(w, np.sqrt(1 - np.dot(w.T, w))))]&lt;br /&gt;
idx.append(lam)&lt;br /&gt;
# solve &lt;span class="caps"&gt;LL&lt;/span&gt;&amp;#8217;x = y in two steps:&lt;br /&gt;
Ltx = linalg.solve_triangular(L, alpha[idx], lower=True)&lt;br /&gt;
gamma = linalg.solve_triangular(L, Ltx, trans=1, lower=True)&lt;br /&gt;
residual = y - np.dot(X[:, idx],&amp;nbsp;gamma)&lt;/p&gt;
&lt;p&gt;return gamma, idx&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;Note that a lot of the code remained unchanged, this is the same
algorithm as before, only the Cholesky trick is used to improve
performance. According to the plot in [&lt;a href="#footnote-3"&gt;3&lt;/a&gt;], we can see that the naive
implementation has oscillations of the reconstruction error due to
numerical instability, while this Cholesky implementation is&amp;nbsp;well-behaved.&lt;/p&gt;
&lt;p&gt;Along with this I also implemented the Gram-based version of this
algorithm, which only needs [latex] X&amp;#8217;X[/latex] and [latex] X&amp;#8217;y[/latex]
(and [latex] {\big|\big|}y{\big|\big|}_2\^2[/latex], in case the
epsilon-parametrization is desired). This is called &lt;strong&gt;Batch &lt;span class="caps"&gt;OMP&lt;/span&gt;&lt;/strong&gt; in
[&lt;a href="#footnote-2"&gt;2&lt;/a&gt;], because it offers speed gains when many signals need to be
sparse coded against the same dictionary [latex] X[/latex]. A lot of
speed is gained because two large matrix multiplications are avoided at
each iteration, but for many datasets, the cost of the precomputations
dominates the procedure. I will not insist on Gram &lt;span class="caps"&gt;OMP&lt;/span&gt; in this post, it
can be found in&amp;nbsp;the &lt;code&gt;scikits.learn&lt;/code&gt; repository [&lt;a href="#footnote-4"&gt;4&lt;/a&gt;].&lt;/p&gt;
&lt;p&gt;Now, the problems with this are a bit more subtle. At this point, I
moved on to code other things, since &lt;span class="caps"&gt;OMP&lt;/span&gt; was passing tests and the
signal recovery example was working. The following issues popped up
during&amp;nbsp;review:&lt;/p&gt;
&lt;p&gt;​1. The lambda stopping condition does not pickle.&lt;br /&gt;
2. For well-constructed signals and data matrices, assuming normal
atoms, [latex] \mathbf{w}[/latex] on line 14 will never have norm
greater than or equal to zero, unless the chosen feature happens to be
dependent of the already chosen set. In theory, this cannot happen,
since we do an orthogonal projection at each step. However, if the
matrix [latex] X[/latex] is not well-behaved (for example, if it has two
identical columns, and [latex] y[/latex] is built using non-zero
coefficients for those columns), then we end up with the square root of
a negative value on line 17.&lt;br /&gt;
3. It was orders of magnitude slower than least-angle regression, given
the same number of nonzero&amp;nbsp;coefficients.&lt;/p&gt;
&lt;p&gt;1 was an easy fix. 2 was a bit tricky since it was a little hidden: the
first time I encountered such an error, I wrongfully assumed that given
that the diagonal of [latex] X_\text{idx}&amp;#8217;X_\text{idx}[/latex] was
unit, then [latex] L[/latex] should also have a unit diagonal, so I
passed the&amp;nbsp;parameter &lt;code&gt;unit_diagonal=True&lt;/code&gt; to &lt;code&gt;linalg.solve_triangular&lt;/code&gt;,
and the plethora of NaN&amp;#8217;s along the diagonal were simply ignored. Let
this show what happens when you don&amp;#8217;t pay attention when&amp;nbsp;coding.&lt;/p&gt;
&lt;p&gt;When I realized my mistake, I first did something I saw&amp;nbsp;in &lt;code&gt;lars_path&lt;/code&gt;
from the scikit: take the absolute value of the argument&amp;nbsp;of &lt;code&gt;sqrt&lt;/code&gt;, and
also ensure it is practically larger than zero. However, tests started
failing randomly. Confusion ensued until the nature of the issue,
discussed above, was discovered. It&amp;#8217;s just not right to take&amp;nbsp;the &lt;code&gt;abs&lt;/code&gt;:
if that argument ends up less than zero, &lt;span class="caps"&gt;OMP&lt;/span&gt; simply cannot proceed and
must stop due to malformed data. The reference implementation from the
website of the authors of [&lt;a href="#footnote-2"&gt;2&lt;/a&gt;] includes explicit &lt;em&gt;early stopping&lt;/em&gt;
conditions for this, along with some other&amp;nbsp;cases.&lt;/p&gt;
&lt;p&gt;At the same time, I started to try a couple of optimizations. The most
obvious thing was the way I was building the matrix [latex] L[/latex]
was clearly suboptimal, reallocating it at each&amp;nbsp;iteration.&lt;/p&gt;
&lt;p&gt;This leads to the following&amp;nbsp;code:&lt;/p&gt;
&lt;p&gt;[sourcecode&amp;nbsp;language=&amp;#8221;Python&amp;#8221;]&lt;/p&gt;
&lt;p&gt;def cholesky_omp(X, y, n_nonzero_coefs, eps=None):&lt;br /&gt;
min_float = np.finfo(X.dtype).eps&lt;br /&gt;
alpha = np.dot(X.T, y)&lt;br /&gt;
residual = y&lt;br /&gt;
n_active = 0&lt;br /&gt;
idx =&amp;nbsp;[]&lt;/p&gt;
&lt;p&gt;max_features = X.shape&lt;a href="#footnote-1"&gt;1&lt;/a&gt; if eps is not None else n_nonzero_coefs&lt;br /&gt;
L = np.empty((max_features, max_features), dtype=X.dtype)&lt;br /&gt;
L[0, 0] =&amp;nbsp;1.&lt;/p&gt;
&lt;p&gt;while 1:&lt;br /&gt;
lam = np.abs(np.dot(X.T, residual)).argmax()&lt;br /&gt;
if lam \&amp;lt; n_active or alpha[lam] ** 2 &amp;gt; min_float:&lt;br /&gt;
# atom already selected or inner product too small&lt;br /&gt;
warn(&amp;#8220;Stopping early&amp;#8221;)&lt;br /&gt;
break&lt;br /&gt;
if n_active &amp;gt; 0:&lt;br /&gt;
# Updates the Cholesky decomposition of X&amp;#8217; X&lt;br /&gt;
w = linalg.solve_triangular(L[:n_active, :n_active],&lt;br /&gt;
np.dot(X[:, idx].T, X[:, lam]),&lt;br /&gt;
lower=True)&lt;br /&gt;
L[n_active, :n_active] = w&lt;br /&gt;
d = np.dot(w.T, w)&lt;br /&gt;
if 1 - d &amp;lt;= min_float: # selected atoms are dependent&lt;br /&gt;
warn(&amp;#8220;Stopping early&amp;#8221;)&lt;br /&gt;
break&lt;br /&gt;
L[n_active, n_active] = np.sqrt(1 - d)&lt;br /&gt;
idx.append(lam)&lt;br /&gt;
# solve &lt;span class="caps"&gt;LL&lt;/span&gt;&amp;#8217;x = y in two steps:&lt;br /&gt;
Ltx = linalg.solve_triangular(L[:n_active, :n_active], alpha[idx],
lower=True)&lt;br /&gt;
gamma = linalg.solve_triangular(L[:n_active, :n_active], Ltx,
trans=1, lower=True)&lt;br /&gt;
residual = y - np.dot(X[:, idx], gamma)&lt;br /&gt;
if eps is not None and np.dot(residual.T, residual) &amp;lt;= eps:&lt;br /&gt;
break&lt;br /&gt;
elif n_active == max_features:&lt;br /&gt;
break&lt;br /&gt;
return gamma, idx&lt;br /&gt;&amp;nbsp;[/sourcecode]&lt;/p&gt;
&lt;p&gt;What should be noted here, apart from the obvious fix for #1, are the
early stopping conditions. It is natural to stop if the same feature
gets picked twice: the residual is always orthogonalized with respect to
the chosen basis, so the only way this could happen is if there would be
no more unused independent regressors. This would either lead to this,
or to the stopping criterion on line 25, depending on which equally
insignificant vector gets picked. The other criterion for early stopping
is if the chosen atom is orthogonal to y, which would make it
uninformative and would again mean that there are no better ones left,
so we might as well quit&amp;nbsp;looking.&lt;/p&gt;
&lt;p&gt;Also, we now make sure that [latex] L[/latex] is preallocated. Note&amp;nbsp;that
&lt;code&gt;np.empty&lt;/code&gt; is marginally faster&amp;nbsp;than &lt;code&gt;np.zeros&lt;/code&gt; because it does not
initialize the array to zero after allocating, so the untouched parts of
the array will contain whatever happened to be in memory before. In our
case, this means only the values above the main diagonal: everything on
and beneath is initialized before access. Luckily,&amp;nbsp;the
&lt;code&gt;linalg.solve_triangular&lt;/code&gt; function ignores what it doesn&amp;#8217;t&amp;nbsp;need.&lt;/p&gt;
&lt;p&gt;This is a robust implementation, but still a couple of times slower than
least-angle regression. In the next part of the article we will see how
we can make it beat &lt;span class="caps"&gt;LARS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span id="footnote-1"&gt;&lt;a href="#footnote-1"&gt;1&lt;/a&gt;&lt;/span&gt; I always wanted to use this word in a
serious context :P&lt;br /&gt;
&lt;span id="footnote-2"&gt;&lt;a href="#footnote-2"&gt;2&lt;/a&gt;&lt;/span&gt; Rubinstein, R., Zibulevsky, M. and
Elad, M., [Efficient Implementation of the K-&lt;span class="caps"&gt;SVD&lt;/span&gt; Algorithm using Batch
Orthogonal Matching Pursuit][] Technical Report - &lt;span class="caps"&gt;CS&lt;/span&gt; Technion, April
2008.&lt;br /&gt;
&lt;span id="footnote-3"&gt;&lt;a href="#footnote-3"&gt;3&lt;/a&gt;&lt;/span&gt; [First thoughts on Orthogonal
Matching Pursuit][] on this blog.&lt;br /&gt;
&lt;span id="footnote-4"&gt;&lt;a href="#footnote-4"&gt;4&lt;/a&gt;&lt;/span&gt; &lt;a href="https://github.com/scikit-learn/scikit-learn/blob/master/scikits/learn/linear_model/omp.py"&gt;omp.py&lt;/a&gt; on&amp;nbsp;github.&lt;/p&gt;</content><category term="scikit-learn"/><category term="efficient"/><category term="numpy"/><category term="omp"/><category term="orthogonal matching pursuit"/><category term="scipy"/><category term="dictionary learning"/><category term="scikit-learn"/><category term="python"/></entry><entry><title>Progress on Orthogonal Matching Pursuit</title><link href="//vene.ro/blog/progress-on-orthogonal-matching-pursuit.html" rel="alternate"/><published>2011-08-02T16:56:00+02:00</published><updated>2011-08-02T16:56:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-08-02:/blog/progress-on-orthogonal-matching-pursuit.html</id><summary type="html">&lt;p&gt;Since orthogonal matching pursuit (&lt;span class="caps"&gt;OMP&lt;/span&gt;) is an important part of signal
processing and therefore crucial to the image processing aspect of
dictionary learning, I am currently focusing on optimizing the &lt;span class="caps"&gt;OMP&lt;/span&gt; code
and making sure it is stable. &lt;span class="caps"&gt;OMP&lt;/span&gt; is a forward method like least-angle
regression, so it is natural …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Since orthogonal matching pursuit (&lt;span class="caps"&gt;OMP&lt;/span&gt;) is an important part of signal
processing and therefore crucial to the image processing aspect of
dictionary learning, I am currently focusing on optimizing the &lt;span class="caps"&gt;OMP&lt;/span&gt; code
and making sure it is stable. &lt;span class="caps"&gt;OMP&lt;/span&gt; is a forward method like least-angle
regression, so it is natural to bench them against one&amp;nbsp;another.&lt;/p&gt;
&lt;p&gt;This has helped find a couple of bottlenecks. Time has been gained by
preallocating the array to store the Cholesky decomposition. Also, using
the &lt;span class="caps"&gt;LAPACK&lt;/span&gt; &lt;code&gt;potrs&lt;/code&gt; function in order to solve a system of the shape
$latex &lt;span class="caps"&gt;LL&lt;/span&gt;&amp;#8217;x=y$ is faster than&amp;nbsp;using &lt;code&gt;solve_triangular&lt;/code&gt; twice.&lt;/p&gt;
&lt;p&gt;I am still trying to optimize the code. We are working hard to make sure
that scikits.learn contributions are up to standards before&amp;nbsp;merging.&lt;/p&gt;</content><category term="scikit-learn"/><category term="omp"/><category term="orthogonal matching pursuit"/><category term="scikit-learn"/></entry><entry><title>SparsePCA in scikits.learn-git</title><link href="//vene.ro/blog/sparsepca-in-scikits-learn-git.html" rel="alternate"/><published>2011-07-19T12:01:00+02:00</published><updated>2011-07-19T12:01:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-07-19:/blog/sparsepca-in-scikits-learn-git.html</id><summary type="html">&lt;p&gt;I am happy to announce that the Sparse &lt;span class="caps"&gt;PCA&lt;/span&gt; code has been reviewed and
merged into the&amp;nbsp;main &lt;code&gt;scikits.learn&lt;/code&gt; repository.&lt;/p&gt;
&lt;p&gt;You can use it if you install the bleeding&amp;nbsp;edge &lt;code&gt;scikits.learn&lt;/code&gt; git
version, by first downloading the source code as explained in the
&lt;a href="http://scikit-learn.sourceforge.net/stable/developers/index.html#git-repo" title="installation user's guide"&gt;user&amp;#8217;s guide&lt;/a&gt;, and then …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am happy to announce that the Sparse &lt;span class="caps"&gt;PCA&lt;/span&gt; code has been reviewed and
merged into the&amp;nbsp;main &lt;code&gt;scikits.learn&lt;/code&gt; repository.&lt;/p&gt;
&lt;p&gt;You can use it if you install the bleeding&amp;nbsp;edge &lt;code&gt;scikits.learn&lt;/code&gt; git
version, by first downloading the source code as explained in the
&lt;a href="http://scikit-learn.sourceforge.net/stable/developers/index.html#git-repo" title="installation user's guide"&gt;user&amp;#8217;s guide&lt;/a&gt;, and then&amp;nbsp;running &lt;code&gt;python setup.py install&lt;/code&gt;.&lt;br /&gt;
[caption id=&amp;#8221;&amp;#8221; align=&amp;#8221;aligncenter&amp;#8221; width=&amp;#8221;400&amp;#8221; caption=&amp;#8221;Sparse &lt;span class="caps"&gt;PCA&lt;/span&gt; on
images of the digit 3&amp;#8221;][&lt;img alt="" src="http://scikit-learn.sourceforge.net/dev/_images/plot_digits_decomposition_4.png" title="Sparse PCA on images of the digit 3" /&gt;]&lt;a href="http://scikit-learn.sourceforge.net/dev/_images/plot_digits_decomposition_4.png" title="Sparse PCA on images of the digit 3"&gt;&lt;/a&gt;[/caption]&lt;br /&gt;
To see what code is needed to produce an image such as the one above,&amp;nbsp;using &lt;code&gt;scikits.learn&lt;/code&gt;. check out this cool &lt;a href="http://scikit-learn.sourceforge.net/dev/auto_examples/decomposition/plot_digits_decomposition.html" title="decomposition example"&gt;decomposition example&lt;/a&gt;
that compares the results of most matrix decomposition models
implemented at the&amp;nbsp;moment.&lt;/p&gt;
&lt;p&gt;There are other new cool things that have been recently merged by other
contributors, such as support for sparse matrices in &lt;a href="http://scikit-learn.sourceforge.net/dev/modules/clustering.html#mini-batch-k-means" title="minibatch K-means"&gt;minibatch
K-means&lt;/a&gt;, and the &lt;a href="http://scikit-learn.sourceforge.net/dev/modules/mixture.html#infinite-gaussian-mixtures-dpgmm-classifier" title="variational infinite gaussian mixture model"&gt;variational infinite gaussian mixture model&lt;/a&gt;, so
I invite you to take a&amp;nbsp;look!&lt;/p&gt;
&lt;p&gt;[&lt;img alt="" src="http://scikit-learn.sourceforge.net/dev/_images/plot_digits_decomposition_4.png" title="Sparse PCA on images of the digit 3" /&gt;]:&amp;nbsp;http://scikit-learn.sourceforge.net/dev/_images/plot_digits_decomposition_4.png&lt;/p&gt;</content><category term="scikit-learn"/><category term="pca"/><category term="principal components analysis"/><category term="scikit-learn"/><category term="sparse pca"/><category term="SparsePCA"/><category term="spca"/></entry><entry><title>K-Means for dictionary learning</title><link href="//vene.ro/blog/k-means-for-dictionary-learning.html" rel="alternate"/><published>2011-07-10T14:27:00+02:00</published><updated>2011-07-10T14:27:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-07-10:/blog/k-means-for-dictionary-learning.html</id><summary type="html">&lt;p&gt;[![Dictionary learned with K-Means on the &lt;span class="caps"&gt;LFW&lt;/span&gt; dataset with whitening
&lt;span class="caps"&gt;PCA&lt;/span&gt;][]][][![Dictionary learned with K-Means on the &lt;span class="caps"&gt;LFW&lt;/span&gt; dataset without
whitening &lt;span class="caps"&gt;PCA&lt;/span&gt;][]][]&lt;/p&gt;
&lt;p&gt;One of the simplest, and yet most heavily constrained form of matrix
factorization, is vector quantization (&lt;span class="caps"&gt;VQ&lt;/span&gt;). Heavily used in image/video
compression, the &lt;span class="caps"&gt;VQ&lt;/span&gt; problem is a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;[![Dictionary learned with K-Means on the &lt;span class="caps"&gt;LFW&lt;/span&gt; dataset with whitening
&lt;span class="caps"&gt;PCA&lt;/span&gt;][]][][![Dictionary learned with K-Means on the &lt;span class="caps"&gt;LFW&lt;/span&gt; dataset without
whitening &lt;span class="caps"&gt;PCA&lt;/span&gt;][]][]&lt;/p&gt;
&lt;p&gt;One of the simplest, and yet most heavily constrained form of matrix
factorization, is vector quantization (&lt;span class="caps"&gt;VQ&lt;/span&gt;). Heavily used in image/video
compression, the &lt;span class="caps"&gt;VQ&lt;/span&gt; problem is a factorization [latex] X=&lt;span class="caps"&gt;WH&lt;/span&gt;[/latex]
where [latex] H[/latex] (our dictionary) is called the codebook and is
designed to cover the cloud of data points effectively, and each line of
[latex] W[/latex] is a unit&amp;nbsp;vector.&lt;/p&gt;
&lt;p&gt;This means that each each data point [latex] x_i[/latex] is
approximated as [latex] x_i \approx h_{k} = \sum_{j=1}\^{r}
\delta_{kj}h_{j}[/latex]. In other words, the closest row vector
(codeword/dictionary atom) [latex] h_k[/latex] of [latex] H[/latex] is
chosen as an approximation, and this is encoded as a unit vector [latex]
(\delta_{k1}, &amp;#8230;, \delta_{kr})[/latex]. The data representation
[latex] W[/latex] is composed of such&amp;nbsp;vectors.&lt;/p&gt;
&lt;p&gt;There is a variation called gain-shape &lt;span class="caps"&gt;VQ&lt;/span&gt; where instead of approximating
each point as one of the codewords, we allow a scalar multiplication
invariance: [latex] x_i \approx \alpha_ih_k[/latex]. This model
requires considerably more storage (each data point needs a floating
point number and an unsigned index, as opposed to just the index), but
it leads to a much better approximation.&lt;br /&gt;
Gain-shape &lt;span class="caps"&gt;VQ&lt;/span&gt; can equivalently be accomplished by normalizing each data
vector prior to fitting the&amp;nbsp;codebook.&lt;/p&gt;
&lt;p&gt;In order to fit a codebook [latex] H[/latex] for efficient &lt;span class="caps"&gt;VQ&lt;/span&gt; use, the
K-Means Clustering [&lt;a href="#footnote-1"&gt;1&lt;/a&gt;] algorithm is a natural thought. K-means is an
iterative algorithm that incrementally improves the dispersion of k
cluster centers in the data space until convergence. The cluster centers
are initialized in a random or procedural fashion, then, at each
iteration, the data points are assigned to the closest cluster center,
which is subsequently moved to the center of the points assigned to&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;scikits.learn.decomposition.KMeansCoder&lt;/code&gt; object from our
work-in-progress dictionary learning toolkit can learn a dictionary from
image patches using the K-Means algorithm, with optional local contrast
normalization and a &lt;span class="caps"&gt;PCA&lt;/span&gt; whitening transform. Using a trained object to
transform data points with orthogonal matching pursuit, with the&amp;nbsp;parameter &lt;code&gt;n_atoms=1&lt;/code&gt; is equivalent to gain-shape &lt;span class="caps"&gt;VQ&lt;/span&gt;. Of course you are
free to use any method of sparse coding such as &lt;span class="caps"&gt;LARS&lt;/span&gt;. The code used to
produce the example images on top of this post can be found in [&lt;a href="#footnote-2"&gt;2&lt;/a&gt;].&lt;/p&gt;
&lt;p&gt;Using K-Means for learning the dictionary does not optimize over linear
combinations of dictionary atoms, like standard dictionary learning
methods do. However, it&amp;#8217;s considerably faster, and Adam Coates and
Andrew Ng suggest in [&lt;a href="#footnote-3"&gt;3&lt;/a&gt;] that as long as the dictionary is filled
with a large enough number of atoms and it covers well enough the cloud
of data (and of future test data) points, then K-Means, or even random
sampling of image patches, can perform remarkably well for some&amp;nbsp;tasks.&lt;/p&gt;
&lt;div id="footnote-1"&gt;
[1] [Wikipedia article on K-Means clustering][]

&lt;/div&gt;
&lt;div id="footnote-2"&gt;
[2] [K-Means Coder example][]

&lt;/div&gt;
&lt;div id="footnote-3"&gt;
[3] [**The importance of encoding versus training with sparse coding and
vector quantization**, Adam Coates and Andrew Y. Ng. In Proceedings of
the Twenty-Eighth International Conference on Machine Learning, 2011.][]

&lt;/div&gt;

&lt;p&gt;[![Dictionary learned with K-Means on the &lt;span class="caps"&gt;LFW&lt;/span&gt; dataset with whitening
  &lt;span class="caps"&gt;PCA&lt;/span&gt;][]]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/07/kmeans_w.png&lt;/p&gt;
&lt;p&gt;[![Dictionary learned with K-Means on the &lt;span class="caps"&gt;LFW&lt;/span&gt; dataset without
  whitening &lt;span class="caps"&gt;PCA&lt;/span&gt;][]]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/07/kmeans_no_w.png&lt;/p&gt;</content><category term="scikit-learn"/><category term="dictionary learning"/><category term="k-means"/><category term="scikit-learn"/><category term="vq"/><category term="Uncategorized"/></entry><entry><title>Image denoising with dictionary learning</title><link href="//vene.ro/blog/image-denoising-with-dictionary-learning.html" rel="alternate"/><published>2011-07-07T20:00:00+02:00</published><updated>2011-07-07T20:00:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-07-07:/blog/image-denoising-with-dictionary-learning.html</id><summary type="html">&lt;p&gt;I am presenting an image denoising example that fully runs under my
local scikits-learn fork. Coming soon near&amp;nbsp;you!&lt;/p&gt;
&lt;p&gt;The 400 square pixels area covering Lena&amp;#8217;s face was distorted by
additive gaussian noise with a standard deviation of 50 (pixel values
are ranged&amp;nbsp;0-256.)&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Lena image denoising using dictionary learning" src="http://localhost:8001/wp-content/uploads/2011/07/denoise3.png" title="Lena denoising" /&gt;][]&lt;/p&gt;
&lt;p&gt;The dictionary contains 100 atoms …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am presenting an image denoising example that fully runs under my
local scikits-learn fork. Coming soon near&amp;nbsp;you!&lt;/p&gt;
&lt;p&gt;The 400 square pixels area covering Lena&amp;#8217;s face was distorted by
additive gaussian noise with a standard deviation of 50 (pixel values
are ranged&amp;nbsp;0-256.)&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Lena image denoising using dictionary learning" src="http://localhost:8001/wp-content/uploads/2011/07/denoise3.png" title="Lena denoising" /&gt;][]&lt;/p&gt;
&lt;p&gt;The dictionary contains 100 atoms of shape 4x4 and was trained using
10000 random patches extracted from the undistorted image. Then, each
one of the four 100 square pixel areas was reconstructed using the
dictionary learning model and a different transform&amp;nbsp;method.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="caps"&gt;OMP&lt;/span&gt;-1 reconstructs each patch as the closest dictionary atom,
    multiplied by a variable coefficient. This is similar to the idea of
    gain-shape vector&amp;nbsp;quantization.&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;OMP&lt;/span&gt;-2 is like &lt;span class="caps"&gt;OMP&lt;/span&gt;-1, but it considers 2 atoms instead of just one.
    This takes advantage of the fact that the natural dictionary atoms
    are of such nature to efficiently represent random image patches
    when&amp;nbsp;combined.&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;LARS&lt;/span&gt; finds a reconstruction of each image patch as a solution to a
    Lasso problem, solved using least angle&amp;nbsp;regression.&lt;/li&gt;
&lt;li&gt;Thresholding is a simple and quick non-linearity that (as it is
    currently implemented, based on [&lt;a href="#footnote-1"&gt;1&lt;/a&gt;], where it is not intended
    for reconstruction but for classification) breaks the local
    brightness of the image fragment. The bottom right fragment was
    forcefully renormalized to stretch fit into the 0-256 range, but
    brightness differences can be&amp;nbsp;seen.&lt;/li&gt;
&lt;/ul&gt;
&lt;div id="footnote-1"&gt;
[1] [**The importance of encoding versus training with sparse coding and
vector quantization**, Adam Coates and Andrew Y. Ng. In Proceedings of
the Twenty-Eighth International Conference on Machine Learning, 2011.][]

&lt;/div&gt;

&lt;p&gt;[&lt;img alt="Lena image denoising using dictionary learning" src="http://localhost:8001/wp-content/uploads/2011/07/denoise3.png" title="Lena denoising" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/07/denoise3.png&lt;/p&gt;</content><category term="scikit-learn"/><category term="denoising"/><category term="dictionary learning"/><category term="scikit-learn"/></entry><entry><title>Dictionary learning sneak peek</title><link href="//vene.ro/blog/dictionary-learning-sneak-peek.html" rel="alternate"/><published>2011-06-24T12:06:00+02:00</published><updated>2011-06-24T12:06:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-06-24:/blog/dictionary-learning-sneak-peek.html</id><summary type="html">&lt;p&gt;Closing in on the goal of integrating J. Mairal&amp;#8217;s dictionary learning in
the scikit, I stitched together a couple of&amp;nbsp;examples.&lt;/p&gt;
&lt;p&gt;The code is not yet integrated according to our standards, but here is
the kind of results you can&amp;nbsp;expect.&lt;/p&gt;
&lt;p&gt;Here is how a dictionary obtained from 8x8 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Closing in on the goal of integrating J. Mairal&amp;#8217;s dictionary learning in
the scikit, I stitched together a couple of&amp;nbsp;examples.&lt;/p&gt;
&lt;p&gt;The code is not yet integrated according to our standards, but here is
the kind of results you can&amp;nbsp;expect.&lt;/p&gt;
&lt;p&gt;Here is how a dictionary obtained from 8x8 patches of Lena looks like.
Pretty much it looks as expected: gabor-like wavelets with different
rotations and shifts, which means things are&amp;nbsp;working!&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Dictionary learned from lena patches" src="http://localhost:8001/wp-content/uploads/2011/06/image.png" title="Dictionary learned from lena patches" /&gt;][]&lt;/p&gt;
&lt;p&gt;And here is how it works for denoising Lena. On the left is the noisy
image and on the right is the reconstruction from a learned dictionary.
The sparse code code producing the result on the right is found using
orthogonal matching&amp;nbsp;pursuit.&lt;/p&gt;
&lt;p&gt;This is by no means a good denoising example, I have no idea at the
moment how to tweak the patch sizes and the model parameters to obtain a
better result. This is just a sneak peek and pretty soon you will see
better&amp;nbsp;stuff!&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Denoising Lena with dictionary learning and OMP" src="http://localhost:8001/wp-content/uploads/2011/06/denoise.png" title="Denoising Lena" /&gt;][]&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Dictionary learned from lena patches" src="http://localhost:8001/wp-content/uploads/2011/06/image.png" title="Dictionary learned from lena patches" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/06/image.png&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Denoising Lena with dictionary learning and OMP" src="http://localhost:8001/wp-content/uploads/2011/06/denoise.png" title="Denoising Lena" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/06/denoise.png&lt;/p&gt;</content><category term="Uncategorized"/><category term="Uncategorized"/></entry><entry><title>Summer of Code roadmap, part 1</title><link href="//vene.ro/blog/summer-of-code-roadmap-part-1.html" rel="alternate"/><published>2011-06-12T14:28:00+02:00</published><updated>2011-06-12T14:28:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-06-12:/blog/summer-of-code-roadmap-part-1.html</id><summary type="html">&lt;p&gt;After a little busy while, I have graduated and entered the summer
vacation, which means time for serious GSoC&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Me on graduation day" src="http://localhost:8001/wp-content/uploads/2011/06/p1080283.jpg" title="Graduation day" /&gt;][]&lt;/p&gt;
&lt;p&gt;So we had a little conference in order to discuss what will be done and
when. We gathered quite a few code snippets since the official start of
the project …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After a little busy while, I have graduated and entered the summer
vacation, which means time for serious GSoC&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Me on graduation day" src="http://localhost:8001/wp-content/uploads/2011/06/p1080283.jpg" title="Graduation day" /&gt;][]&lt;/p&gt;
&lt;p&gt;So we had a little conference in order to discuss what will be done and
when. We gathered quite a few code snippets since the official start of
the project, but it&amp;#8217;s now time to talk about integration and pull&amp;nbsp;requests.&lt;/p&gt;
&lt;p&gt;Here is the&amp;nbsp;plan:&lt;/p&gt;
&lt;h4 id="sparsepca"&gt;SparsePCA&lt;a class="headerlink" href="#sparsepca" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;First pull request due: &lt;strong&gt;June 15&lt;/strong&gt;&lt;br /&gt;
This will be the use case I blogged about &lt;a href="http://venefrombucharest.wordpress.com/2011/05/23/sparse-pca/" title="Sparse PCA"&gt;before&lt;/a&gt;. Specifically, we
want to learn a dictionary of sparse atoms, but representations of the
data will be&amp;nbsp;dense.&lt;/p&gt;
&lt;h4 id="sparsecoder"&gt;SparseCoder&lt;a class="headerlink" href="#sparsecoder" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;First pull request due: &lt;strong&gt;June 25&lt;/strong&gt;&lt;br /&gt;
This is the transpose of the SparsePCA problem. We are learning the
optimal, dense dictionary for sparse representations of the&amp;nbsp;data.&lt;/p&gt;
&lt;h4 id="kmeanscoder"&gt;KMeansCoder&lt;a class="headerlink" href="#kmeanscoder" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;First pull request due: &lt;strong&gt;June 30&lt;/strong&gt;&lt;br /&gt;
This method builds the dictionary out of cluster centers found by&amp;nbsp;K-means.&lt;/p&gt;
&lt;h4 id="onlinesparsecoder"&gt;OnlineSparseCoder&lt;a class="headerlink" href="#onlinesparsecoder" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;First pull request due: &lt;strong&gt;July 10&lt;/strong&gt;&lt;br /&gt;
This will involve the online learning tricks suggested in Julien
Mairal&amp;#8217;s work and will allow for faster computations of both sparse &lt;span class="caps"&gt;PCA&lt;/span&gt;
and sparse coding. In the case of sparse coding, it will make use of the
scikits.learn &lt;span class="caps"&gt;API&lt;/span&gt; for online&amp;nbsp;learning.&lt;/p&gt;
&lt;p&gt;While I will try to keep the deadlines for the initial pull requests as
strictly as I can, we did not establish deadlines for merging, since
this will depend on more factors. As long as the pull requests are up,
the code review system will push it forward towards the merge. The focus
is on teamwork and on feedback cycles as short as possible, as opposed
to falling into the trap of delaying work until the night before the&amp;nbsp;deadline.&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Me on graduation day" src="http://localhost:8001/wp-content/uploads/2011/06/p1080283.jpg" title="Graduation day" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/06/p1080283.jpg&lt;/p&gt;</content><category term="scikit-learn"/><category term="gsoc"/><category term="Uncategorized"/></entry><entry><title>First thoughts on Orthogonal Matching Pursuit</title><link href="//vene.ro/blog/first-thoughts-on-orthogonal-matching-pursuit.html" rel="alternate"/><published>2011-05-30T13:02:00+02:00</published><updated>2011-05-30T13:02:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-05-30:/blog/first-thoughts-on-orthogonal-matching-pursuit.html</id><summary type="html">&lt;p&gt;I am working on implementing the Orthogonal Matching Pursuit (&lt;span class="caps"&gt;OMP&lt;/span&gt;)
algorithm for the scikit. It is an elegant algorithm (that almost writes
itself in Numpy!) to compute a greedy approximation to the solution of a
sparse coding&amp;nbsp;problem:&lt;/p&gt;
&lt;p&gt;$$ \text{argmin} \big|\big|\gamma\big|\big|_0 \text{ subject
to }\big …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am working on implementing the Orthogonal Matching Pursuit (&lt;span class="caps"&gt;OMP&lt;/span&gt;)
algorithm for the scikit. It is an elegant algorithm (that almost writes
itself in Numpy!) to compute a greedy approximation to the solution of a
sparse coding&amp;nbsp;problem:&lt;/p&gt;
&lt;p&gt;$$ \text{argmin} \big|\big|\gamma\big|\big|_0 \text{ subject
to }\big|\big|x-D\gamma\big|\big|_2\^2 \leq&amp;nbsp;\epsilon$$&lt;/p&gt;
&lt;p&gt;or (in a different&amp;nbsp;parametrization)&lt;/p&gt;
&lt;p&gt;$$ \text{argmin} \big|\big|x - D\gamma\big|\big|_2\^2\text{
subject to }\big|\big|\gamma\big|\big|_0 \leq&amp;nbsp;m$$&lt;/p&gt;
&lt;p&gt;The second formulation is interesting in that it gives one of the few
algorithms for sparse coding that can control the actual number of
non-zero entries in the solution. Some dictionary learning methods need
this (I&amp;#8217;m thinking of K-&lt;span class="caps"&gt;SVD&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Both problems are solved by the same algorithm, with a different
stopping condition. The gist of it is to include at each iteration, the
atom with the highest correlation to the current residual. However, as
opposed to regular Matching Pursuit, here, after choosing the atom, the
input signal is orthogonally projected to the space spanned by the
chosen atoms. This involves the solution of a least squares problem at
each step. However, because the problem is almost the same at each
iteration, only with one more column added to the matrix, this can be
easily solved by maintaining a &lt;span class="caps"&gt;QR&lt;/span&gt; or Cholesky decomposition of the
dictionary matrix that is updated at each&amp;nbsp;step.&lt;/p&gt;
&lt;p&gt;Rubinstein et al. [1] came up with a clever method to optimize the
calculations, based on the fact that usually in practice we never have
to find a sparse coding for a single signal, but usually for a batch.
They called this method Batch &lt;span class="caps"&gt;OMP&lt;/span&gt;, and it is based on a straightforward
modification of the Cholesky update algorithm, taking advantage of
precomputing the Gram matrix [latex]&amp;nbsp;G=D&amp;#8217;D[/latex].&lt;/p&gt;
&lt;p&gt;Based on my experiments, their batch update is the fastest, even though
it lags behind if invoked with too small a batch. As soon as I make sure
the implementation is robust and ready for use, I will make some&amp;nbsp;benchmarks.&lt;/p&gt;
&lt;p&gt;Update: Here&amp;#8217;s a little proof that it works!&lt;br /&gt;
[&lt;img alt="Stem plot for sparse signals recovered by OMP" src="http://localhost:8001/wp-content/uploads/2011/06/omp.png" title="Orthogonal Matching Pursuit sparse signal recovery" /&gt;][]&lt;/p&gt;
&lt;p&gt;Update 2: Here&amp;#8217;s a little benchmark:&lt;br /&gt;
[&lt;img alt="Orthogonal Matching Pursuit benchmark" src="http://localhost:8001/wp-content/uploads/2011/06/omp_bench.png" title="OMP benchmark, time and error" /&gt;][]&lt;br /&gt;
[1]
http://www.cs.technion.ac.il/\~ronrubin/Publications/&lt;span class="caps"&gt;KSVD&lt;/span&gt;-&lt;span class="caps"&gt;OMP&lt;/span&gt;-v2.pdf&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Stem plot for sparse signals recovered by OMP" src="http://localhost:8001/wp-content/uploads/2011/06/omp.png" title="Orthogonal Matching Pursuit sparse signal recovery" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/06/omp.png&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Orthogonal Matching Pursuit benchmark" src="http://localhost:8001/wp-content/uploads/2011/06/omp_bench.png" title="OMP benchmark, time and error" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/06/omp_bench.png&lt;/p&gt;</content><category term="scikit-learn"/><category term="Uncategorized"/><category term="dictionary learning"/><category term="omp"/><category term="orthogonal matching pursuit"/></entry><entry><title>Sparse PCA</title><link href="//vene.ro/blog/sparse-pca.html" rel="alternate"/><published>2011-05-23T15:19:00+02:00</published><updated>2011-05-23T15:19:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-05-23:/blog/sparse-pca.html</id><summary type="html">&lt;p&gt;I have been working on the integration into the scikits.learn codebase
of a sparse principal components analysis (SparsePCA) algorithm coded by
Gaël and Alexandre and based on [[1]][]. Because the name &amp;#8220;sparse &lt;span class="caps"&gt;PCA&lt;/span&gt;&amp;#8221;
has some inherent ambiguity, I will describe in greater depth what
problem we are actually solving …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been working on the integration into the scikits.learn codebase
of a sparse principal components analysis (SparsePCA) algorithm coded by
Gaël and Alexandre and based on [[1]][]. Because the name &amp;#8220;sparse &lt;span class="caps"&gt;PCA&lt;/span&gt;&amp;#8221;
has some inherent ambiguity, I will describe in greater depth what
problem we are actually solving, and what it can be used&amp;nbsp;for.&lt;/p&gt;
&lt;h1 id="the-problem"&gt;The problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Mathematically, this implementation of Sparse &lt;span class="caps"&gt;PCA&lt;/span&gt;&amp;nbsp;solves:&lt;/p&gt;
&lt;p&gt;$latex (U\^*,
V\^*)=\underset{U,V}{\mathrm{argmin\,}}\frac{1}{2}||X-&lt;span class="caps"&gt;UV&lt;/span&gt;||_2\^2+\alpha||V||_1$&lt;/p&gt;
&lt;p&gt;with $latex || U_k ||_2 = 1$ for all $latex 0 \leq k \&amp;lt;&amp;nbsp;n_{atoms}$&lt;/p&gt;
&lt;p&gt;This looks really abstract so let&amp;#8217;s try to interpret it. We are looking
for a matrix factorization $latex &lt;span class="caps"&gt;UV&lt;/span&gt;$ of $latex X \in
\mathbf{R}\^{n_{samples}\times n_{features}}$, just like in
ordinary &lt;span class="caps"&gt;PCA&lt;/span&gt;. The interpretation is that the $latex n_{atoms}$ lines
of $latex V$ are the extracted components, while the lines of $latex
U$ are the coordinates of the samples in this&amp;nbsp;projection.&lt;/p&gt;
&lt;p&gt;The most important difference between this and &lt;span class="caps"&gt;PCA&lt;/span&gt; is that we enforce
sparsity on the &lt;em&gt;components&lt;/em&gt;. In other words, we look for a
representation of the data as a linear combination of sparse&amp;nbsp;signals.&lt;/p&gt;
&lt;p&gt;Another difference is that, unlike in &lt;span class="caps"&gt;PCA&lt;/span&gt;, here we don&amp;#8217;t constrain U to
be orthogonal, just to consist of normalized column vectors. There are
different approaches where this constraint appears too, and they are on
the list for this summer, but I&amp;nbsp;digress.&lt;/p&gt;
&lt;h1 id="the-approach"&gt;The approach&lt;a class="headerlink" href="#the-approach" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;As usual, such optimization problems are solved by alternatively
minimizing one of the variables while keeping the other fixed, until
convergence is&amp;nbsp;reached.&lt;/p&gt;
&lt;p&gt;The update of $latex V$ (the dictionary) is computed as the solution
of a Lasso least squares problem.  We allow the user to choose between
the least angle regression method (&lt;span class="caps"&gt;LARS&lt;/span&gt;) or stochastic gradient descent
as algorithms to solve the Lasso&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;The update of $latex U$ is block coordinate descent with warm restart.
This is a batch adaptation of an online algorithm proposed by Mairal et
al. in&amp;nbsp;[[1]][].&lt;/p&gt;
&lt;h1 id="sparse-pca-as-a-transformer"&gt;Sparse &lt;span class="caps"&gt;PCA&lt;/span&gt; as a transformer&lt;a class="headerlink" href="#sparse-pca-as-a-transformer" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Of course, in order to be of practical use, the code needs to be
refactored into a scikits.learn transformer object, just&amp;nbsp;like
&lt;code&gt;scikits.learn.decomposition.pca&lt;/code&gt;. This means that the optimization
problem described above corresponds to the fitting stage. The post-fit
state of the transformer is given by the learned components (the matrix
$latex V$&amp;nbsp;above).&lt;/p&gt;
&lt;p&gt;In order to transform new data according to the learned sparse &lt;span class="caps"&gt;PCA&lt;/span&gt; model
(for example, prior to classification of the test data), we simply need
to do a least squares projection of the new data on the sparse&amp;nbsp;components.&lt;/p&gt;
&lt;h1 id="what-is-it-good-for"&gt;What is it good for?&lt;a class="headerlink" href="#what-is-it-good-for" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;For applications such as text and image processing, its great advantage
is interpretability. When running a regular &lt;span class="caps"&gt;PCA&lt;/span&gt; on a set of documents in
bag of words format, we can find an interesting visualisation on a
couple of components, and it can show discrimination or clusters. The
biggest problem is that the maximum variance components found by &lt;span class="caps"&gt;PCA&lt;/span&gt;
have very dense expressions as linear combinations of the initial
features. In practice, sometimes interpretation is made by simply
marking the $latex k$ variables with the highest coefficients in this
representation, and basically interpreting as if the rest are truncated
to 0 (this has been taught to me in a class on &lt;span class="caps"&gt;PCA&lt;/span&gt;&amp;nbsp;interpretation).&lt;/p&gt;
&lt;p&gt;Such an approximation can be highly misleading, and now we offer you the
sparse &lt;span class="caps"&gt;PCA&lt;/span&gt; code that can extract components with only few non-zero
coefficients, and therefore easy to&amp;nbsp;interpret.&lt;/p&gt;
&lt;p&gt;For image data, sparse &lt;span class="caps"&gt;PCA&lt;/span&gt; should extract local components such as,
famously, parts of the face in the case of face&amp;nbsp;recognition.&lt;/p&gt;
&lt;p&gt;Personally I can&amp;#8217;t wait to have it ready for the scikit so that I can
play with it in some of my projects. I have two tasks where I can&amp;#8217;t wait
to see the results: one is related to &lt;a href="http://venefrombucharest.wordpress.com/2011/04/14/a-look-at-romanian-verbs-with-scikits-learn/" title="A look at Romanian verbs with scikits-learn"&gt;Romanian infinitives&lt;/a&gt;, where
&lt;span class="caps"&gt;PCA&lt;/span&gt; revealed structure, and I would love to see how it looks with sparse
n-gram components. The other task is to plug it in as feature extractor
for handwritten digit classification, for my undergraduate&amp;nbsp;thesis.&lt;/p&gt;
&lt;p&gt;&lt;span id="footnote_1"&gt;[1] &lt;a href="http://www.di.ens.fr/sierra/pdfs/icml09.pdf"&gt;http://www.di.ens.fr/sierra/pdfs/icml09.pdf&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;[[1]]:&amp;nbsp;#footnote_1&lt;/p&gt;</content><category term="scikit-learn"/><category term="dictionary learning"/><category term="pca"/><category term="sparse pca"/><category term="SparsePCA"/><category term="spca"/><category term="scikit-learn"/></entry><entry><title>Customizing scikits.learn for a specific text analysis task</title><link href="//vene.ro/blog/customizing-scikits-learn-for-a-specific-text-analysis-task.html" rel="alternate"/><published>2011-04-29T14:33:00+02:00</published><updated>2011-04-29T14:33:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-04-29:/blog/customizing-scikits-learn-for-a-specific-text-analysis-task.html</id><summary type="html">&lt;p&gt;Scikits.learn is a great general library, but machine learning has so
many different application, that it is often very helpful to be able to
extend its &lt;span class="caps"&gt;API&lt;/span&gt; to better integrate with your code. With scikits.learn,
this is extremely easy to do using inheritance and using the pipeline&amp;nbsp;module …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Scikits.learn is a great general library, but machine learning has so
many different application, that it is often very helpful to be able to
extend its &lt;span class="caps"&gt;API&lt;/span&gt; to better integrate with your code. With scikits.learn,
this is extremely easy to do using inheritance and using the pipeline&amp;nbsp;module.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While continuing the &lt;a href="http://venefrombucharest.wordpress.com/2011/04/14/a-look-at-romanian-verbs-with-scikits-learn/" title="A look at Romanian verbs with scikits-learn"&gt;morphophonetic analysis of Romanian verbal
forms&lt;/a&gt;, I found the need to streamline my workflow to allow for more
complex models. There were a lot of free model parameters and it would
have been painful to interactively tweak everything in order to find a
good&amp;nbsp;combination&lt;/p&gt;
&lt;p&gt;In my case, I needed to read a file containing infinitives and labels
corresponding to conjugation groups, and run a linear support vector
classifier on this data. The &lt;span class="caps"&gt;SVC&lt;/span&gt; has its C parameter that needs to be
tweaked, but I also had some ideas that arose from the images in my old
post. There, I compared the way the data looked when represented as
differently sized n-gram features. Furthermore, I compared the count
features (ie. features indicating the number of times an n-gram occurs
in a string) with binary features (ie. indicating only whether the
n-gram occurs in the string or not). It looked to me like, for such a
low-level text analysis task, using counts only adds&amp;nbsp;noise.&lt;/p&gt;
&lt;p&gt;For this reason,&amp;nbsp;the &lt;code&gt;feature_extraction.text.CountVectorizer&lt;/code&gt; was not
enough for me. It only returns count features. There was also another
thing that needed to be adjusted: by default, its analyzer uses a
preprocessor that strips accented characters, and I had strong reasons
to believe that Romanian diacritics are very relevant for the learning
task. So, I needed to extend the&amp;nbsp;vectorizer.&lt;/p&gt;
&lt;h2 id="the-solution"&gt;The solution&lt;a class="headerlink" href="#the-solution" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The code I came up with is &lt;a href="https://github.com/vene/misc-nlp/blob/master/conjugation/grid_search_example/preprocess.py"&gt;here&lt;/a&gt;. I tried to build a class that would
be as specific to my needs as possible. It is important to retain the
full &lt;span class="caps"&gt;API&lt;/span&gt;, however. Note&amp;nbsp;the &lt;code&gt;y=None&lt;/code&gt; parameter in the fit functions. Its
necessity will become clear in a&amp;nbsp;moment.&lt;/p&gt;
&lt;p&gt;Another tricky part was exposing&amp;nbsp;the &lt;code&gt;max_n&lt;/code&gt; parameter from the inner
analyzer. This was not really natural, but it simplified the
constructions later&amp;nbsp;on.&lt;/p&gt;
&lt;p&gt;My &lt;code&gt;InfinitivesExtractor&lt;/code&gt; class builds a data matrix from a list of
strings. After using it, the data needs to be passed to the classifier,
an instance&amp;nbsp;of &lt;code&gt;svm.LinearSVC&lt;/code&gt;.&amp;nbsp;The &lt;code&gt;pipeline&lt;/code&gt;module in scikits.learn
allows us to plug components into eachother in order to build a more
complex object. In this case, we would like a classifier that receives a
string as input, and directly outputs its label. We wouldn&amp;#8217;t want the
user to have to manually use the feature extractor prior to&amp;nbsp;classification.&lt;/p&gt;
&lt;p&gt;The pipeline is very easy to&amp;nbsp;build:  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;pipeline = Pipeline([('extr', InfinitivesExtractor()), ('svc', LinearSVC(multi_class=True))])&lt;/code&gt;&lt;br /&gt;
The pipeline object now works exactly as expected: we can call fit and
predict on it. It also exposes the parameters of its constituents, by
prefixing them with the name of that component. For example, the support
vector machine&amp;#8217;s C parameter can be accessed as&amp;nbsp;pipeline.svc__C.&lt;/p&gt;
&lt;p&gt;All that is left now is to see whether this is a good model, and what
combination of parameters makes it work the best. Scikits.learn provides
a great tool for choosing the parameters:&amp;nbsp;the &lt;code&gt;grid_search&lt;/code&gt; module. When
working with models like support vector machines, model parameters (such
as the radial basis kernel width) usually need to be chosen by cross
validation, because intuition doesn&amp;#8217;t help much when dealing with high
dimensional&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;Grid search allows the definition of a discrete range of values for
multiple parameters. Then, for each combination of parameters, it fits
and evaluates a model using cross-validation, and the model with the
best score is the winner. Because we combined the components into a
pipeline, it is very easy to run grid search on the combined model, and
to simultaneously tweak the settings both for the extractor and for the&amp;nbsp;classifier.&lt;/p&gt;
&lt;p&gt;After running the grid search using the code &lt;a href="https://github.com/vene/misc-nlp/blob/master/conjugation/grid_search_example/gridsearch.py"&gt;here&lt;/a&gt;, I found that
indeed, using binary features instead of occurence counts improves
performance. I also found that the optimal n-gram length is 5, but the
gain is not that big when compared to a length of 3, which generates a
lot less&amp;nbsp;features.&lt;/p&gt;
&lt;h2 id="conclusions"&gt;Conclusions&lt;a class="headerlink" href="#conclusions" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hope that I managed to show the strength of a well-designed &lt;span class="caps"&gt;API&lt;/span&gt;.
Because of it, it would be very easy to add, for example, an extra layer
for dimensionality reduction before classification. It would only
require an extra item in the pipeline constructor. A call from a
web-based frontend, for example, would be very short and simple. Because
of the consistency in the scikits.learn classes, we can write cleaner
and better code, and therefore work with greater&amp;nbsp;efficiency.&lt;/p&gt;</content><category term="scikit-learn"/><category term="nlp"/><category term="scikit-learn"/></entry><entry><title>An overview of dictionary learning: Terminology</title><link href="//vene.ro/blog/an-overview-of-dictionary-learning-terminology.html" rel="alternate"/><published>2011-04-15T14:10:00+02:00</published><updated>2011-04-15T14:10:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-04-15:/blog/an-overview-of-dictionary-learning-terminology.html</id><summary type="html">&lt;p&gt;My GSoC proposal is titled &amp;#8220;Dictionary learning in scikits.learn&amp;#8221; and in
the project, I plan to implement methods used in state of the art
research and industry applications in signal and image processing. In
this post, I want to clarify the terminology&amp;nbsp;used.&lt;/p&gt;
&lt;p&gt;Usually the terms &lt;em&gt;dictionary learning&lt;/em&gt; and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My GSoC proposal is titled &amp;#8220;Dictionary learning in scikits.learn&amp;#8221; and in
the project, I plan to implement methods used in state of the art
research and industry applications in signal and image processing. In
this post, I want to clarify the terminology&amp;nbsp;used.&lt;/p&gt;
&lt;p&gt;Usually the terms &lt;em&gt;dictionary learning&lt;/em&gt; and &lt;em&gt;sparse coding&lt;/em&gt; are used
interchangably. Also my proposal contains methods such as Sparse &lt;span class="caps"&gt;PCA&lt;/span&gt;
which are technically not &lt;em&gt;sparse coding&lt;/em&gt; but closely related&amp;nbsp;problems.&lt;/p&gt;
&lt;p&gt;The basic idea is the approximation of a signal vector [latex] x \in
\mathbb{R}\^d[/latex] by a linear combination of components, as good as
possible, under certain constraints. This can be formulated as a basic
(unconstrained) loss function measuring the quality of the
approximation: [latex] \mathcal{L}(x, D, \alpha) = \big|\big|x -
D\alpha\big|\big|\^2_2, D \in \mathbb{R}\^{d \times r}, \alpha
\in \mathbb{R}\^r[/latex], where [latex] r[/latex] is the dimension of
the dictionary (the number of &lt;em&gt;components)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;When working with a dataset of more signal vectors, the overall basic
loss for such an approximation is [latex] \mathcal{L}(X, D, A) =
\sum_{i = 1}\^N \mathcal{L}(x_i, D, \alpha_i) = \big|\big|X -
&lt;span class="caps"&gt;DA&lt;/span&gt;\big|\big|\^2_F[/latex]. Minimizing such a loss function amounts to
finding the closest (in the Frobenius sense) matrix factorization
[latex] &lt;span class="caps"&gt;DA&lt;/span&gt;[/latex] that approximates the data matrix [latex]&amp;nbsp;X[/latex]&lt;/p&gt;
&lt;p&gt;This generic problem is called a &lt;strong&gt;matrix factorization problem&lt;/strong&gt;. Many
classical problems are matrix factorization problems with additional
constraints. For example, &lt;span class="caps"&gt;PCA&lt;/span&gt; is a matrix factorization that constrains
[latex] D[/latex] to be orthogonal. &lt;span class="caps"&gt;NMF&lt;/span&gt; constrains both [latex]
D[/latex] and [latex] A[/latex] to have no negative elements. Sparse
variants of these two decompositions are useful for getting local
components, such as parts of faces. These are obtained by adding an
aditional constraint on the dictionary&amp;nbsp;columns.&lt;/p&gt;
&lt;p&gt;It can  be sometimes useful to consider the dictionary fixed. The signal
processing community has introduced over the years many such
dictionaries, for examples wavelets. These are used, for example, in the
&lt;span class="caps"&gt;JPEG2000&lt;/span&gt; compression&amp;nbsp;standard.&lt;/p&gt;
&lt;p&gt;A very useful represenation is when the dictionary is &lt;em&gt;overcomplete&lt;/em&gt;
([latex] r &amp;gt; d[/latex]). The wavelets are an example of this. Given
such a dictionary, we are interested in an efficient encoding of a
vector [latex] x[/latex], in the sense of sparseness: we want to use as
few dictionary components as possible in our representation. Such a
solution is the vector [latex]\alpha[/latex] minimizing [latex]
\mathcal{L}(x, D, \alpha) +
\lambda\big|\big|\alpha\big|\big|_1[/latex] but other
sparsity-inducing constraints can be used. Such a vector is a &lt;strong&gt;sparse
coding&lt;/strong&gt; of [latex] x[/latex] and it can be solved using algorithms such
as least-angle regression and orthogonal matching&amp;nbsp;pursuit.&lt;/p&gt;
&lt;p&gt;However, we are not limited to using precomputed dictionaries. The term
&lt;strong&gt;dictionary learning&lt;/strong&gt; refers to methods of inferring, given [latex]
X[/latex], a (usually overcomplete) dictionary that will perform good at
sparsely encoding the data in [latex] X[/latex]. Such methods are more
expensive than using precomputed dictionaries, but they perform better,
since the dictionary is optimized for the current&amp;nbsp;dataset.&lt;/p&gt;
&lt;p&gt;Because usually such loss functions are non-convex in [latex] D[/latex]
and [latex] A[/latex] simultaneously, dictionary learning algorithms
alternate between minimizing each while keeping the other fixed. The
step that minimizes [latex] D[/latex] is sometimes called the
&lt;strong&gt;dictionary update&lt;/strong&gt; step, and the one minimizing [latex] A[/latex] is
(similarily to the case where the dictionary is always fixed) the
&lt;strong&gt;sparse coding&lt;/strong&gt; step. Dictionary learning algorithms differ in the
method used for each of this&amp;nbsp;step.&lt;/p&gt;
&lt;p&gt;To resume, many problems can be posed as matrix factorization problems.
Depending on the constraints imposed, the problem becomes interesting
for different applications. Dictionary learning is very good for image
reconstruction. Matrix decompositions with sparse undercomplete
dictionaries such as Sparse &lt;span class="caps"&gt;PCA&lt;/span&gt; can be used to find local features that
constitute the dataset, for example parts of faces, for a dataset of
facial images. &lt;span class="caps"&gt;NMF&lt;/span&gt; can be used in both under and overcomplete settings
and it offers a good model for additive data such as text or images. We
are interested in these variants and they are planned for implementation
in my GSoC&amp;nbsp;proposal.&lt;/p&gt;
&lt;p&gt;Julien Mairal&amp;#8217;s presentation of his work in this domain, available
&lt;a href="http://videolectures.net/icml09_mairal_odlsc/" title="Mairal dictionary learning"&gt;here&lt;/a&gt;, shows the theoretical background of such methods, along with
examples showing state of the art results in image&amp;nbsp;processing.&lt;/p&gt;</content><category term="dictionary learning"/><category term="dictionary learning"/><category term="scikit-learn"/><category term="Uncategorized"/></entry><entry><title>Newton interpolation and numerical differentiation</title><link href="//vene.ro/blog/newton-interpolation-and-numerical-differentiation.html" rel="alternate"/><published>2011-04-15T13:34:00+02:00</published><updated>2011-04-15T13:34:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-04-15:/blog/newton-interpolation-and-numerical-differentiation.html</id><summary type="html">&lt;p&gt;I am sharing some Python code code that I wrote as a school assignment.
This computes the Newton form of the interpolation polynomial of a given
set of points, and allows for the evaluation of both the polynomial and
its derivative, at a given point. This is an accurate way …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am sharing some Python code code that I wrote as a school assignment.
This computes the Newton form of the interpolation polynomial of a given
set of points, and allows for the evaluation of both the polynomial and
its derivative, at a given point. This is an accurate way of estimating
the derivative of a complicated&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;Initially it plots the function, the interpolating polynomial and its
derivative. When clicking on the plot, the tangent to the interpolating
polynomial at the horizontal position of the mouse cursor is&amp;nbsp;plotted.&lt;/p&gt;
&lt;p&gt;It can be found here: &lt;a href="https://gist.github.com/921554"&gt;https://gist.github.com/921554&lt;/a&gt;&lt;/p&gt;</content><category term="python"/><category term="differentiation"/><category term="interpolation"/><category term="matplotlib"/><category term="newton"/><category term="numerical"/><category term="numpy"/><category term="python"/></entry><entry><title>A look at Romanian verbs with scikits-learn</title><link href="//vene.ro/blog/a-look-at-romanian-verbs-with-scikits-learn.html" rel="alternate"/><published>2011-04-14T01:40:00+02:00</published><updated>2011-04-14T01:40:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-04-14:/blog/a-look-at-romanian-verbs-with-scikits-learn.html</id><summary type="html">&lt;p&gt;One of the problems we tackled here at my university is one as old as
the modern Romanian language. It is a problem for linguists, as well as
for foreigners trying to learn the language. We call it the root
alternations&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;Similar to French and other languages, Romanian verbs …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of the problems we tackled here at my university is one as old as
the modern Romanian language. It is a problem for linguists, as well as
for foreigners trying to learn the language. We call it the root
alternations&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;Similar to French and other languages, Romanian verbs are split into
four groups with different conjugation patterns. Except for the
irregular verbs, this categorization is performed based on the suffix of
the infinitive. However, the conjugation is not straightforward even
within these classes, because many verbs exhibit alternations in their
root. For example, the verb &lt;em&gt;a purta&lt;/em&gt; (to wear) becomes &lt;em&gt;eu port&lt;/em&gt; (I
wear) but &lt;em&gt;el poartă&lt;/em&gt; (he wears). It can be seen that the letter &lt;em&gt;o&lt;/em&gt; in
the root changes to &lt;em&gt;oa&lt;/em&gt; during conjugation. This makes learning the
language quite difficult, because we have no rules to describe when
these changes&amp;nbsp;occur.&lt;/p&gt;
&lt;p&gt;Attempts to formalize such rules from a computer scientific point of
view date back to &lt;span class="caps"&gt;G. C.&lt;/span&gt; Moisil in 1960. Such (incomplete) rules can be
formulated as context-sensitive grammars, since the alternations are
determined by the context in which certain characters&amp;nbsp;appear.&lt;/p&gt;
&lt;p&gt;This leads to the idea of analyzing the verbs from a machine learning
point of view: what can we find out by looking at n-gram representation
of the&amp;nbsp;infinitives?&lt;/p&gt;
&lt;p&gt;This is easy to do within scikits.learn.&amp;nbsp;The &lt;code&gt;feature_extraction.text&lt;/code&gt;
package contains all the necessary tools:&amp;nbsp;the &lt;code&gt;CharNGramExtractor&lt;/code&gt;,
which builds all the n-grams of a string, for n in an interval. Then,&amp;nbsp;a
&lt;code&gt;CountVectorizer&lt;/code&gt; is built on top of the extractor. Its purpose is to
extract the features out of a list of documents and transform them into
a matrix representation of token counts. By postprocessing this matrix
we can obtain a binary representation, indicating only whether a token
occurs in a document or not, instead of the&amp;nbsp;count.&lt;/p&gt;
&lt;p&gt;In this case, documents are Romanian infinitives. This means we are
limited to using short n-grams, because the documents are themselves
short. There is also the question whether anything relevant can be found
out of such a representation which does not encode a lot of&amp;nbsp;information.&lt;/p&gt;
&lt;p&gt;After building the data matrix from the list of verbs, I plotted a 2D
&lt;span class="caps"&gt;PCA&lt;/span&gt; projection and here are the results. I am only posting a teaser for
now, but the results are&amp;nbsp;encouraging:&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Romanian infinitives as 2D projection" src="http://localhost:8001/wp-content/uploads/2011/04/infinitives_pca.png" title="infinitives_pca" /&gt;][]&lt;/p&gt;
&lt;p&gt;From the image it is clear that n-gram representations of the
infinitives induce clusters. Further results suggest that for certain
subclasses of the dataset, such a representation (or even a simpler one)
is enough to clearly answer whether a verb does not exhibit
alternations. This encourages further exploration of this path,
especially supervised and semi-supervised&amp;nbsp;approaches.&lt;/p&gt;
&lt;p&gt;[&lt;img alt="Romanian infinitives as 2D projection" src="http://localhost:8001/wp-content/uploads/2011/04/infinitives_pca.png" title="infinitives_pca" /&gt;]:&amp;nbsp;http://localhost:8001/wp-content/uploads/2011/04/infinitives_pca.png&lt;/p&gt;</content><category term="nlp"/><category term="alternations"/><category term="computational linguistics"/><category term="infinitives"/><category term="pca"/><category term="principal components analysis"/><category term="nlp"/><category term="scikit-learn"/></entry><entry><title>Tweaking matplotlib subplots for pretty results</title><link href="//vene.ro/blog/tweaking-matplotlib-subplots-for-pretty-results.html" rel="alternate"/><published>2011-04-04T20:53:00+02:00</published><updated>2011-04-04T20:53:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-04-04:/blog/tweaking-matplotlib-subplots-for-pretty-results.html</id><summary type="html">&lt;p&gt;When plotting multiple subplots using matplotlib, the axes rarely look
pretty with the default configuration. Since matplotlib figures are
abstract objects, designed for consistency in print as well as on
screen, tweaking their layout can get&amp;nbsp;tricky.&lt;/p&gt;
&lt;h3 id="an-example"&gt;An example&lt;a class="headerlink" href="#an-example" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following code is taken from the &lt;a href="http://scikit-learn.sourceforge.net/auto_examples/applications/plot_face_recognition.html" title="face recognition example"&gt;face recognition example&lt;/a&gt; in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When plotting multiple subplots using matplotlib, the axes rarely look
pretty with the default configuration. Since matplotlib figures are
abstract objects, designed for consistency in print as well as on
screen, tweaking their layout can get&amp;nbsp;tricky.&lt;/p&gt;
&lt;h3 id="an-example"&gt;An example&lt;a class="headerlink" href="#an-example" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following code is taken from the &lt;a href="http://scikit-learn.sourceforge.net/auto_examples/applications/plot_face_recognition.html" title="face recognition example"&gt;face recognition example&lt;/a&gt; in
scikits.learn:&lt;br /&gt;
&lt;code&gt;pl.figure(figsize=(1.8 * n_col, 2.4 * n_row))&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This is very confusing at first, for somebody used to work on screen:
the quantities in there are actually inches! These are converted
implicitly to pixels through the dpi parameter, which is left as default
(80 dpi) in this&amp;nbsp;example.&lt;/p&gt;
&lt;p&gt;Then, it gets even worse: In order to tweak the positioning of the
subplots, this is what is&amp;nbsp;done:  &lt;/p&gt;
&lt;p&gt;&lt;code&gt;pl.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.35)&lt;/code&gt;&lt;br /&gt;
Now, all of these are percents of the image height/width. The margins
are sort of like &lt;span class="caps"&gt;CSS&lt;/span&gt;-style margins, only relative to the bottom left
corner. In other&amp;nbsp;words, &lt;code&gt;right=.99&lt;/code&gt; means that the right margin is 1%
away from the right&amp;nbsp;edge.&lt;/p&gt;
&lt;p&gt;The&amp;nbsp;parameters &lt;code&gt;hspace&lt;/code&gt; and &lt;code&gt;wspace&lt;/code&gt; control the spacing between the
subplots. However these are kind of hard to get right, because,
obviously, there are more settings than there are degrees of&amp;nbsp;freedom.&lt;/p&gt;
&lt;h3 id="my-tip"&gt;My tip&lt;a class="headerlink" href="#my-tip" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;On my system, the default matplotlib backend is TkAgg. The matplotlib
backend controls the graphical environment that builds the plot windows,
as well as the rendering engine used. TkAgg has a &amp;#8220;configure subplots&amp;#8221;
button that opens a popup window with sliders to visually adjust the
parameters above. The problem is that the sliders are unlabeled, so I
needed to do an heuristic by first setting the parameters by hand and
then exploring the direction in which they need to be&amp;nbsp;changed.&lt;/p&gt;
&lt;p&gt;When I tried different backends, I found that WXAgg has labeled sliders.
This means you can adjust your subplots visually and you will have the
parameter values to use in the call&amp;nbsp;to &lt;code&gt;subplots_adjust&lt;/code&gt; in one&amp;nbsp;go!&lt;/p&gt;
&lt;p&gt;You can set your backend to WXAgg by adding the&amp;nbsp;line &lt;code&gt;backend: WXAgg&lt;/code&gt; in
your &lt;a href="http://matplotlib.sourceforge.net/users/customizing.html#the-matplotlibrc-file" title="Customizing matplotlib"&gt;matplotlibrc file&lt;/a&gt;.&lt;/p&gt;</content><category term="python"/><category term="matplotlib"/><category term="python"/></entry><entry><title>On setuptools subpackages</title><link href="//vene.ro/blog/on-setuptools-subpackages.html" rel="alternate"/><published>2011-04-04T15:01:00+02:00</published><updated>2011-04-04T15:01:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-04-04:/blog/on-setuptools-subpackages.html</id><summary type="html">&lt;p&gt;Today, I spent more than two hours trying to figure out why, despite
things working out fine in my development scikits.learn&amp;nbsp;folder,
&lt;code&gt;python setup.py install&lt;/code&gt; would completely ignore the module I
refactored into a&amp;nbsp;subpackage.&lt;/p&gt;
&lt;p&gt;I imagined that simply adding it to the&amp;nbsp;parent &lt;code&gt;__init__.py __all__&lt;/code&gt;
attribute …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today, I spent more than two hours trying to figure out why, despite
things working out fine in my development scikits.learn&amp;nbsp;folder,
&lt;code&gt;python setup.py install&lt;/code&gt; would completely ignore the module I
refactored into a&amp;nbsp;subpackage.&lt;/p&gt;
&lt;p&gt;I imagined that simply adding it to the&amp;nbsp;parent &lt;code&gt;__init__.py __all__&lt;/code&gt;
attribute would do, I kind of thought that setuptools automatically
finds the&amp;nbsp;subpackages.&lt;/p&gt;
&lt;p&gt;At first I thought of looking&amp;nbsp;in &lt;code&gt;setup.py&lt;/code&gt;, but I only examined the one
in the topmost directory, which, in the case of scikits.learn, is two
degrees of separation away from the actual setup.py that takes care of
subpackages (ie. I was looking&amp;nbsp;at &lt;code&gt;/setup.py&lt;/code&gt; instead&amp;nbsp;of
&lt;code&gt;/scikits/learn/setup.py&lt;/code&gt;).  This had me fooled for a&amp;nbsp;while.&lt;/p&gt;
&lt;p&gt;The steps to add a working and installable module to a python
setuptools-based project are as&amp;nbsp;follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add&amp;nbsp;a &lt;code&gt;__init__.py&lt;/code&gt; file in the folder&amp;nbsp;(ie.
    &lt;code&gt;/scikits/learn/decomposition/__init__.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If the module requires compiling or any special attention, add an&amp;nbsp;appropriate  &lt;code&gt;__setup__.py&lt;/code&gt; file in the&amp;nbsp;folder.&lt;/li&gt;
&lt;li&gt;Update&amp;nbsp;the &lt;code&gt;__init__.py __all__&lt;/code&gt; attribute in the parent folder&amp;nbsp;(ie.
    &lt;code&gt;/scikits/learn/__init__.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Update&amp;nbsp;the &lt;code&gt;setup.py&lt;/code&gt; in the parent folder&amp;nbsp;(ie.
    &lt;code&gt;/scikits/learn/setup.py&lt;/code&gt;) by adding something like:&lt;br /&gt;
&lt;code&gt;config.add_subpackage('decomposition')&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Don&amp;#8217;t forget to do the same for the tests&amp;nbsp;subfolder!&lt;/p&gt;
&lt;h2 id="conclusions"&gt;Conclusions&lt;a class="headerlink" href="#conclusions" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While wasting so much time due to a simple beginner&amp;#8217;s mistake is not
very pleasant, I am not frustrated with setuptools. On the contrary, now
that I understand it better I can appreciate its flexibility and
clarity, when compared to, for example, MSBuild and Visual Studio
project files. Just one more reason to love&amp;nbsp;Python!&lt;/p&gt;</content><category term="python"/><category term="python"/><category term="setuptools"/></entry><entry><title>My first scikits.learn coding sprint</title><link href="//vene.ro/blog/my-first-scikits-learn-coding-sprint.html" rel="alternate"/><published>2011-04-02T20:12:00+02:00</published><updated>2011-04-02T20:12:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-04-02:/blog/my-first-scikits-learn-coding-sprint.html</id><summary type="html">&lt;p&gt;The fifth &lt;a href="http://scikit-learn.sourceforge.net/" title="scikits.learn"&gt;scikits.learn&lt;/a&gt; coding sprint took place Friday, April 1st
2011. For anyone who is not familiar with it, scikits.learn is a fast
and easy to use machine learning toolkit for the pylab environment
(Python, NumPy, SciPy,&amp;nbsp;Matplotlib.)&lt;/p&gt;
&lt;p&gt;This was a good opportunity for me to get code …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The fifth &lt;a href="http://scikit-learn.sourceforge.net/" title="scikits.learn"&gt;scikits.learn&lt;/a&gt; coding sprint took place Friday, April 1st
2011. For anyone who is not familiar with it, scikits.learn is a fast
and easy to use machine learning toolkit for the pylab environment
(Python, NumPy, SciPy,&amp;nbsp;Matplotlib.)&lt;/p&gt;
&lt;p&gt;This was a good opportunity for me to get code reviews by the developers
in order to bring my &lt;span class="caps"&gt;NMF&lt;/span&gt; code up to standards, so that it can be merged.
Though I live far from every nucleus of scikits-learn developers, I
efficiently participated via &lt;span class="caps"&gt;IRC&lt;/span&gt;. This way, I also got the chance to
help out a bit on Mathieu Blondel&amp;#8217;s Kernel &lt;span class="caps"&gt;PCA&lt;/span&gt; code, which will also be
merged into main&amp;nbsp;soon.&lt;/p&gt;
&lt;h2 id="how-it-felt-like"&gt;How it felt like&lt;a class="headerlink" href="#how-it-felt-like" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Short answer:&amp;nbsp;awesome!&lt;/p&gt;
&lt;p&gt;Slightly longer answer: Everybody was very encouraging  and helpful.
They gave me a lot of feedback from which I learned a lot, and they
manifested the intention to merge soon. It is a pleasure to work on
projects that you like and use, especially when the projects leaders and
collaborators are so good to work&amp;nbsp;with.&lt;/p&gt;
&lt;p&gt;But the main reason why it makes me feel so good is that I&amp;#8217;m proud to
able to contribute on a project that I consider very significant and the
best in the field from many points of&amp;nbsp;view.&lt;/p&gt;
&lt;h2 id="what-i-got-done"&gt;What I got done&lt;a class="headerlink" href="#what-i-got-done" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most of my work was on the non-negative matrix factorization module that
I began some while back, but only intermitently worked on. It is now a
solid module with high test coverage, documentation, and a cool simple
example showing a sparse set of features for the digits dataset in
scikits.learn.  Apart from all the minor fixes in overall code quality
and cleanliness, probably what is the most relevant is the improvement
and the study of the initialization methods. I will look into this
further and document it on this blog, the point is that the choice of
initialization method greatly influences the speed of convergence, and
in the case of a high-tolerance setting, also the error obtained. Some
initializations are more fit for sparsity settings, while others are
more fit for dense&amp;nbsp;settings.&lt;/p&gt;
&lt;p&gt;I have a theory that I plan to test out, regarding the use of different
initialization methods for components and for data in a sparse&amp;nbsp;setting.&lt;/p&gt;
&lt;h2 id="what-i-learned"&gt;What I learned&lt;a class="headerlink" href="#what-i-learned" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I think my greatest improvement was in terms of workflow and efficiency.
While my code was under review, I was receiving frequent comments on my
git pull request, and eventually I ended up responding to some comments
even before they were posted :). I sent small fixes as pull requests to
help other developers as much as I could. Before scikits.learn I had
never worked on a project with so many developers, and I think I handled
it well, even though I asked once or twice on the &lt;span class="caps"&gt;IRC&lt;/span&gt; channel for pieces
of&amp;nbsp;git-fu.&lt;/p&gt;
&lt;p&gt;I learned that it&amp;#8217;s difficult to tweak matplotlib subplots! I&amp;#8217;m still
staring at Alexandre Gramfort&amp;#8217;s tweak in my example and I have no idea
what he did to make it look so good. But I&amp;#8217;ll figure it out soon, I&amp;#8217;m&amp;nbsp;sure.&lt;/p&gt;
&lt;p&gt;I also learned a lot more about the intricacies of the scikits.learn
APIs, the philosophy of ease of use, and the project tree in&amp;nbsp;general.&lt;/p&gt;
&lt;p&gt;In short, the coding sprint has been a great and rewarding experience,
for which I thank all of you guys&amp;nbsp;there!&lt;/p&gt;</content><category term="scikit-learn"/><category term="coding sprint"/><category term="scikit-learn"/></entry><entry><title>About</title><link href="//vene.ro/blog/about.html" rel="alternate"/><published>2011-03-30T08:59:00+02:00</published><updated>2011-03-30T08:59:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-03-30:/blog/about.html</id><content type="html">&lt;p&gt;My name is Vlad, I am a master&amp;#8217;s student at the University of Bucharest,
I work there at the Centre for Computational Linguistics, and I am a
contributor to the Python machine learning library &lt;a href="http://scikit-learn.org" title="scikit-learn"&gt;scikit-learn&lt;/a&gt;.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Hello world!</title><link href="//vene.ro/blog/hello-world-2.html" rel="alternate"/><published>2011-03-30T08:59:00+02:00</published><updated>2011-03-30T08:59:00+02:00</updated><author><name>vene</name></author><id>tag:vene.ro,2011-03-30:/blog/hello-world-2.html</id><summary type="html">&lt;p&gt;This is the blog where I will post updates regarding my work
on&lt;a href="http://scikit-learn.sourceforge.net/" title="scikits-learn"&gt;scikits-learn.&lt;/a&gt; I am applying as a GSoC 2011 student with the &lt;span class="caps"&gt;PSF&lt;/span&gt;
for work on this machine learning&amp;nbsp;library.&lt;/p&gt;
&lt;p&gt;I will also post related work that I do in the machine learning&amp;nbsp;field.&lt;/p&gt;
&lt;p&gt;I hope to prove …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is the blog where I will post updates regarding my work
on&lt;a href="http://scikit-learn.sourceforge.net/" title="scikits-learn"&gt;scikits-learn.&lt;/a&gt; I am applying as a GSoC 2011 student with the &lt;span class="caps"&gt;PSF&lt;/span&gt;
for work on this machine learning&amp;nbsp;library.&lt;/p&gt;
&lt;p&gt;I will also post related work that I do in the machine learning&amp;nbsp;field.&lt;/p&gt;
&lt;p&gt;I hope to prove an enjoyable read for everybody who stumbles on to this
site, or for those who will be forced to review it&amp;nbsp;:)&lt;/p&gt;</content><category term="Uncategorized"/><category term="Uncategorized"/></entry></feed>