blob: ef0c407c75a277900e1ad268be98eb291c75ce1d [file] [log] [blame]
<!DOCTYPE html><html><head><title>DASE Components Explained (Complementary Purchase)</title><meta charset="utf-8"/><meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><meta class="swiftype" name="title" data-type="string" content="DASE Components Explained (Complementary Purchase)"/><link rel="canonical" href="https://docs.prediction.io/templates/complementarypurchase/dase/"/><link href="/images/favicon/normal-b330020a.png" rel="shortcut icon"/><link href="/images/favicon/apple-c0febcf2.png" rel="apple-touch-icon"/><link href="//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet"/><link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet"/><link href="/stylesheets/application-3598c7d7.css" rel="stylesheet" type="text/css"/><script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js"></script><script src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script><script src="//use.typekit.net/pqo0itb.js"></script><script>try{Typekit.load({ async: true });}catch(e){}</script></head><body><div id="global"><header><div class="container" id="header-wrapper"><div class="row"><div class="col-sm-12"><div id="logo-wrapper"><span id="drawer-toggle"></span><a href="#"></a><a href="http://predictionio.incubator.apache.org/"><img alt="PredictionIO" id="logo" src="/images/logos/logo-ee2b9bb3.png"/></a></div><div id="menu-wrapper"><div id="header-nav-options-wrapper"><ul><li><a href="/">Install & Doc</a></li> <li><a href="/support">Support</a></li> </ul></div><div id="pill-wrapper"><a class="pill left" href="//templates.prediction.io/">TEMPLATES</a> <a class="pill right" href="//github.com/apache/incubator-predictionio/">OPEN SOURCE</a></div></div><img class="mobile-search-bar-toggler hidden-md hidden-lg" src="/images/icons/search-glass-704bd4ff.png"/></div></div></div></header><div id="search-bar-row-wrapper"><div class="container-fluid" id="search-bar-row"><div class="row"><div class="col-md-9 col-sm-11 col-xs-11"><div class="hidden-md hidden-lg" id="mobile-page-heading-wrapper"><p>PredictionIO Docs</p><h4>DASE Components Explained (Complementary Purchase)</h4></div><h4 class="hidden-sm hidden-xs">PredictionIO Docs</h4></div><div class="col-md-3 col-sm-1 col-xs-1 hidden-md hidden-lg"><img id="left-menu-indicator" src="/images/icons/down-arrow-dfe9f7fe.png"/></div><div class="col-md-3 col-sm-12 col-xs-12 swiftype-wrapper"><div class="swiftype"><form class="search-form"><img class="search-box-toggler hidden-xs hidden-sm" src="/images/icons/search-glass-704bd4ff.png"/><div class="search-box"><img src="/images/icons/search-glass-704bd4ff.png"/><input type="text" id="st-search-input" class="st-search-input" placeholder="Search Doc..."/></div><img class="swiftype-row-hider hidden-md hidden-lg" src="/images/icons/drawer-toggle-active-fcbef12a.png"/></form></div></div><div class="mobile-left-menu-toggler hidden-md hidden-lg"></div></div></div></div><div id="page" class="container-fluid"><div class="row"><div id="left-menu-wrapper" class="col-md-3"><nav id="nav-main"><ul><li class="level-1"><a class="expandible" href="/"><span>Apache PredictionIO (incubating) Documentation</span></a><ul><li class="level-2"><a class="final" href="/"><span>Welcome to Apache PredictionIO (incubating)</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Getting Started</span></a><ul><li class="level-2"><a class="final" href="/start/"><span>A Quick Intro</span></a></li><li class="level-2"><a class="final" href="/install/"><span>Installing Apache PredictionIO (incubating)</span></a></li><li class="level-2"><a class="final" href="/start/download/"><span>Downloading an Engine Template</span></a></li><li class="level-2"><a class="final" href="/start/deploy/"><span>Deploying Your First Engine</span></a></li><li class="level-2"><a class="final" href="/start/customize/"><span>Customizing the Engine</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Integrating with Your App</span></a><ul><li class="level-2"><a class="final" href="/appintegration/"><span>App Integration Overview</span></a></li><li class="level-2"><a class="expandible" href="/sdk/"><span>List of SDKs</span></a><ul><li class="level-3"><a class="final" href="/sdk/java/"><span>Java & Android SDK</span></a></li><li class="level-3"><a class="final" href="/sdk/php/"><span>PHP SDK</span></a></li><li class="level-3"><a class="final" href="/sdk/python/"><span>Python SDK</span></a></li><li class="level-3"><a class="final" href="/sdk/ruby/"><span>Ruby SDK</span></a></li><li class="level-3"><a class="final" href="/sdk/community/"><span>Community Powered SDKs</span></a></li></ul></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Deploying an Engine</span></a><ul><li class="level-2"><a class="final" href="/deploy/"><span>Deploying as a Web Service</span></a></li><li class="level-2"><a class="final" href="/cli/#engine-commands"><span>Engine Command-line Interface</span></a></li><li class="level-2"><a class="final" href="/deploy/engineparams/"><span>Setting Engine Parameters</span></a></li><li class="level-2"><a class="final" href="/deploy/enginevariants/"><span>Deploying Multiple Engine Variants</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Customizing an Engine</span></a><ul><li class="level-2"><a class="final" href="/customize/"><span>Learning DASE</span></a></li><li class="level-2"><a class="final" href="/customize/dase/"><span>Implement DASE</span></a></li><li class="level-2"><a class="final" href="/customize/troubleshooting/"><span>Troubleshooting Engine Development</span></a></li><li class="level-2"><a class="final" href="/api/current/#package"><span>Engine Scala APIs</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Collecting and Analyzing Data</span></a><ul><li class="level-2"><a class="final" href="/datacollection/"><span>Event Server Overview</span></a></li><li class="level-2"><a class="final" href="/cli/#event-server-commands"><span>Event Server Command-line Interface</span></a></li><li class="level-2"><a class="final" href="/datacollection/eventapi/"><span>Collecting Data with REST/SDKs</span></a></li><li class="level-2"><a class="final" href="/datacollection/eventmodel/"><span>Events Modeling</span></a></li><li class="level-2"><a class="final" href="/datacollection/webhooks/"><span>Unifying Multichannel Data with Webhooks</span></a></li><li class="level-2"><a class="final" href="/datacollection/channel/"><span>Channel</span></a></li><li class="level-2"><a class="final" href="/datacollection/batchimport/"><span>Importing Data in Batch</span></a></li><li class="level-2"><a class="final" href="/datacollection/analytics/"><span>Using Analytics Tools</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Choosing an Algorithm(s)</span></a><ul><li class="level-2"><a class="final" href="/algorithm/"><span>Built-in Algorithm Libraries</span></a></li><li class="level-2"><a class="final" href="/algorithm/switch/"><span>Switching to Another Algorithm</span></a></li><li class="level-2"><a class="final" href="/algorithm/multiple/"><span>Combining Multiple Algorithms</span></a></li><li class="level-2"><a class="final" href="/algorithm/custom/"><span>Adding Your Own Algorithms</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>ML Tuning and Evaluation</span></a><ul><li class="level-2"><a class="final" href="/evaluation/"><span>Overview</span></a></li><li class="level-2"><a class="final" href="/evaluation/paramtuning/"><span>Hyperparameter Tuning</span></a></li><li class="level-2"><a class="final" href="/evaluation/evaluationdashboard/"><span>Evaluation Dashboard</span></a></li><li class="level-2"><a class="final" href="/evaluation/metricchoose/"><span>Choosing Evaluation Metrics</span></a></li><li class="level-2"><a class="final" href="/evaluation/metricbuild/"><span>Building Evaluation Metrics</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>System Architecture</span></a><ul><li class="level-2"><a class="final" href="/system/"><span>Architecture Overview</span></a></li><li class="level-2"><a class="final" href="/system/anotherdatastore/"><span>Using Another Data Store</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Engine Template Gallery</span></a><ul><li class="level-2"><a class="final" href="http://templates.prediction.io"><span>Browse</span></a></li><li class="level-2"><a class="final" href="/community/submit-template/"><span>Submit your Engine as a Template</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Demo Tutorials</span></a><ul><li class="level-2"><a class="final" href="/demo/tapster/"><span>Comics Recommendation Demo</span></a></li><li class="level-2"><a class="final" href="/demo/community/"><span>Community Contributed Demo</span></a></li><li class="level-2"><a class="final" href="/demo/textclassification/"><span>Text Classification Engine Tutorial</span></a></li></ul></li><li class="level-1"><a class="expandible" href="/community/"><span>Getting Involved</span></a><ul><li class="level-2"><a class="final" href="/community/contribute-code/"><span>Contribute Code</span></a></li><li class="level-2"><a class="final" href="/community/contribute-documentation/"><span>Contribute Documentation</span></a></li><li class="level-2"><a class="final" href="/community/contribute-sdk/"><span>Contribute a SDK</span></a></li><li class="level-2"><a class="final" href="/community/contribute-webhook/"><span>Contribute a Webhook</span></a></li><li class="level-2"><a class="final" href="/community/projects/"><span>Community Projects</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Getting Help</span></a><ul><li class="level-2"><a class="final" href="/resources/faq/"><span>FAQs</span></a></li><li class="level-2"><a class="final" href="/support/"><span>Community Support</span></a></li><li class="level-2"><a class="final" href="/support/#enterprise-support"><span>Enterprise Support</span></a></li></ul></li><li class="level-1"><a class="expandible" href="#"><span>Resources</span></a><ul><li class="level-2"><a class="final" href="/resources/intellij/"><span>Developing Engines with IntelliJ IDEA</span></a></li><li class="level-2"><a class="final" href="/resources/upgrade/"><span>Upgrade Instructions</span></a></li><li class="level-2"><a class="final" href="/resources/glossary/"><span>Glossary</span></a></li></ul></li></ul></nav></div><div class="col-md-9 col-sm-12"><div class="content-header hidden-md hidden-lg"><div id="page-title"><h1>DASE Components Explained (Complementary Purchase)</h1></div></div><div id="table-of-content-wrapper"><h5>On this page</h5><aside id="table-of-contents"><ul> <li> <a href="#the-engine-design">The Engine Design</a> </li> <li> <a href="#data">Data</a> </li> <li> <a href="#algorithm">Algorithm</a> </li> <li> <a href="#serving">Serving</a> </li> </ul> </aside><hr/><a id="edit-page-link" href="https://github.com/apache/incubator-predictionio/tree/livedoc/docs/manual/source/templates/complementarypurchase/dase.html.md.erb"><img src="/images/icons/edit-pencil-d6c1bb3d.png"/>Edit this page</a></div><div class="content-header hidden-sm hidden-xs"><div id="page-title"><h1>DASE Components Explained (Complementary Purchase)</h1></div></div><div class="content"><p>PredictionIO&#39;s DASE architecture brings the separation-of-concerns design principle to predictive engine development. DASE stands for the following components of an engine:</p> <ul> <li><strong>D</strong>ata - includes Data Source and Data Preparator</li> <li><strong>A</strong>lgorithm(s)</li> <li><strong>S</strong>erving</li> <li><strong>E</strong>valuator</li> </ul> <p><p>Let&#39;s look at the code and see how you can customize the engine you built from the Complementary Purchase Engine Template.</p><div class="alert-message note"><p>Evaluator will not be covered in this tutorial.</p></div></p><h2 id='the-engine-design' class='header-anchors'>The Engine Design</h2><p>As you can see from the Quick Start, <em>MyComplementaryPurchase</em> takes a JSON prediction query, e.g. <code>{ &quot;items&quot; : [&quot;s2i1&quot;], &quot;num&quot; : 3 }</code>, and return a JSON predicted result. In MyComplementaryPurchase/src/main/scala/<strong><em>Engine.scala</em></strong>, the <code>Query</code> case class defines the format of such <strong>query</strong>:</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2</pre></td><td class="code"><pre><span class="k">case</span> <span class="k">class</span> <span class="nc">Query</span><span class="o">(</span><span class="n">items</span><span class="k">:</span> <span class="kt">Set</span><span class="o">[</span><span class="kt">String</span><span class="o">],</span> <span class="n">num</span><span class="k">:</span> <span class="kt">Int</span><span class="o">)</span>
<span class="k">extends</span> <span class="nc">Serializable</span>
</pre></td></tr></tbody></table> </div> <p>The <code>PredictedResult</code> case class defines the format of <strong>predicted result</strong>, such as</p><div class="highlight json"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21</pre></td><td class="code"><pre><span class="p">{</span><span class="w">
</span><span class="s2">"rules"</span><span class="p">:[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="s2">"cond"</span><span class="p">:[</span><span class="s2">"s2i1"</span><span class="p">],</span><span class="w">
</span><span class="s2">"itemScores"</span><span class="p">:[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="s2">"item"</span><span class="p">:</span><span class="s2">"s2i2"</span><span class="p">,</span><span class="w">
</span><span class="s2">"support"</span><span class="p">:</span><span class="mf">0.2</span><span class="p">,</span><span class="w">
</span><span class="s2">"confidence"</span><span class="p">:</span><span class="mf">0.9090909090909091</span><span class="p">,</span><span class="w">
</span><span class="s2">"lift"</span><span class="p">:</span><span class="mf">3.787878787878788</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="s2">"item"</span><span class="p">:</span><span class="s2">"s2i3"</span><span class="p">,</span><span class="w">
</span><span class="s2">"support"</span><span class="p">:</span><span class="mf">0.14</span><span class="p">,</span><span class="w">
</span><span class="s2">"confidence"</span><span class="p">:</span><span class="mf">0.6363636363636364</span><span class="p">,</span><span class="w">
</span><span class="s2">"lift"</span><span class="p">:</span><span class="mf">3.535353535353535</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table> </div> <p>with:</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9</pre></td><td class="code"><pre><span class="k">case</span> <span class="k">class</span> <span class="nc">PredictedResult</span><span class="o">(</span><span class="n">rules</span><span class="k">:</span> <span class="kt">Array</span><span class="o">[</span><span class="kt">Rule</span><span class="o">])</span>
<span class="k">extends</span> <span class="nc">Serializable</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">Rule</span><span class="o">(</span><span class="n">cond</span><span class="k">:</span> <span class="kt">Set</span><span class="o">[</span><span class="kt">String</span><span class="o">],</span> <span class="n">itemScores</span><span class="k">:</span> <span class="kt">Array</span><span class="o">[</span><span class="kt">ItemScore</span><span class="o">])</span>
<span class="k">extends</span> <span class="nc">Serializable</span>
<span class="k">case</span> <span class="k">class</span> <span class="nc">ItemScore</span><span class="o">(</span>
<span class="n">item</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">support</span><span class="k">:</span> <span class="kt">Double</span><span class="o">,</span> <span class="n">confidence</span><span class="k">:</span> <span class="kt">Double</span><span class="o">,</span> <span class="n">lift</span><span class="k">:</span> <span class="kt">Double</span>
<span class="o">)</span> <span class="k">extends</span> <span class="nc">Serializable</span>
</pre></td></tr></tbody></table> </div> <p>Finally, <code>ComplementaryPurchaseEngine</code> is the <em>Engine Factory</em> that defines the components this engine will use: Data Source, Data Preparator, Algorithm(s) and Serving components.</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9</pre></td><td class="code"><pre><span class="k">object</span> <span class="nc">ComplementaryPurchaseEngine</span> <span class="k">extends</span> <span class="nc">IEngineFactory</span> <span class="o">{</span>
<span class="k">def</span> <span class="n">apply</span><span class="o">()</span> <span class="k">=</span> <span class="o">{</span>
<span class="k">new</span> <span class="nc">Engine</span><span class="o">(</span>
<span class="n">classOf</span><span class="o">[</span><span class="kt">DataSource</span><span class="o">],</span>
<span class="n">classOf</span><span class="o">[</span><span class="kt">Preparator</span><span class="o">],</span>
<span class="nc">Map</span><span class="o">(</span><span class="s">"algo"</span> <span class="o">-&gt;</span> <span class="n">classOf</span><span class="o">[</span><span class="kt">Algorithm</span><span class="o">]),</span>
<span class="n">classOf</span><span class="o">[</span><span class="kt">Serving</span><span class="o">])</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></td></tr></tbody></table> </div> <p>Each DASE component of the <code>ComplementaryPurchaseEngine</code> will be explained below.</p><h2 id='data' class='header-anchors'>Data</h2><p>In the DASE architecture, data is prepared by 2 components sequentially: <em>DataSource</em> and <em>DataPreparator</em>. They take data from the data store and prepare them for Algorithm.</p><h3 id='data-source' class='header-anchors'>Data Source</h3><p>In MyComplementaryPurchase/src/main/scala/<strong><em>DataSource.scala</em></strong>, the <code>readTraining</code> method of class <code>DataSource</code> reads and selects data from the <em>Event Store</em> (data store of the <em>Event Server</em>). It returns <code>TrainingData</code>.</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35</pre></td><td class="code"><pre><span class="k">case</span> <span class="k">class</span> <span class="nc">DataSourceParams</span><span class="o">(</span><span class="n">appName</span><span class="k">:</span> <span class="kt">String</span><span class="o">)</span> <span class="k">extends</span> <span class="nc">Params</span>
<span class="k">class</span> <span class="nc">DataSource</span><span class="o">(</span><span class="k">val</span> <span class="n">dsp</span><span class="k">:</span> <span class="kt">DataSourceParams</span><span class="o">)</span>
<span class="k">extends</span> <span class="nc">PDataSource</span><span class="o">[</span><span class="kt">TrainingData</span>,
<span class="kt">EmptyEvaluationInfo</span>, <span class="kt">Query</span>, <span class="kt">EmptyActualResult</span><span class="o">]</span> <span class="o">{</span>
<span class="nd">@transient</span> <span class="k">lazy</span> <span class="k">val</span> <span class="n">logger</span> <span class="k">=</span> <span class="nc">Logger</span><span class="o">[</span><span class="kt">this.</span><span class="k">type</span><span class="o">]</span>
<span class="k">override</span>
<span class="k">def</span> <span class="n">readTraining</span><span class="o">(</span><span class="n">sc</span><span class="k">:</span> <span class="kt">SparkContext</span><span class="o">)</span><span class="k">:</span> <span class="kt">TrainingData</span> <span class="o">=</span> <span class="o">{</span>
<span class="c1">// get all "user" "buy" "item" events
</span> <span class="k">val</span> <span class="n">buyEvents</span><span class="k">:</span> <span class="kt">RDD</span><span class="o">[</span><span class="kt">BuyEvent</span><span class="o">]</span> <span class="k">=</span> <span class="nc">PEventStore</span><span class="o">.</span><span class="n">find</span><span class="o">(</span>
<span class="n">appName</span> <span class="k">=</span> <span class="n">dsp</span><span class="o">.</span><span class="n">appName</span><span class="o">,</span>
<span class="n">entityType</span> <span class="k">=</span> <span class="nc">Some</span><span class="o">(</span><span class="s">"user"</span><span class="o">),</span>
<span class="n">eventNames</span> <span class="k">=</span> <span class="nc">Some</span><span class="o">(</span><span class="nc">List</span><span class="o">(</span><span class="s">"buy"</span><span class="o">)),</span>
<span class="n">targetEntityType</span> <span class="k">=</span> <span class="nc">Some</span><span class="o">(</span><span class="nc">Some</span><span class="o">(</span><span class="s">"item"</span><span class="o">)))(</span><span class="n">sc</span><span class="o">)</span>
<span class="o">.</span><span class="n">map</span> <span class="o">{</span> <span class="n">event</span> <span class="k">=&gt;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">new</span> <span class="nc">BuyEvent</span><span class="o">(</span>
<span class="n">user</span> <span class="k">=</span> <span class="n">event</span><span class="o">.</span><span class="n">entityId</span><span class="o">,</span>
<span class="n">item</span> <span class="k">=</span> <span class="n">event</span><span class="o">.</span><span class="n">targetEntityId</span><span class="o">.</span><span class="n">get</span><span class="o">,</span>
<span class="n">t</span> <span class="k">=</span> <span class="n">event</span><span class="o">.</span><span class="n">eventTime</span><span class="o">.</span><span class="n">getMillis</span>
<span class="o">)</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">{</span>
<span class="k">case</span> <span class="n">e</span><span class="k">:</span> <span class="kt">Exception</span> <span class="o">=&gt;</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="o">(</span><span class="n">s</span><span class="s">"Cannot convert ${event} to BuyEvent. ${e}"</span><span class="o">)</span>
<span class="k">throw</span> <span class="n">e</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}.</span><span class="n">cache</span><span class="o">()</span>
<span class="k">new</span> <span class="nc">TrainingData</span><span class="o">(</span><span class="n">buyEvents</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></td></tr></tbody></table> </div> <p>PredictionIO automatically loads the parameters of <em>datasource</em> specified in MyComplementaryPurchase/<strong><em>engine.json</em></strong>, including <em>appName</em>, to <code>dsp</code>.</p><p>In <strong><em>engine.json</em></strong>:</p><div class="highlight shell"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9</pre></td><td class="code"><pre><span class="o">{</span>
...
<span class="s2">"datasource"</span>: <span class="o">{</span>
<span class="s2">"params"</span> : <span class="o">{</span>
<span class="s2">"appName"</span>: <span class="s2">"MyApp1"</span>
<span class="o">}</span>
<span class="o">}</span>,
...
<span class="o">}</span>
</pre></td></tr></tbody></table> </div> <p>In <code>readTraining()</code>, <code>PEventStore</code> is an object which provides function to access data that is collected by PredictionIO Event Server.</p><p>This Complementary Purchase Engine Template requires &quot;buy&quot; events.</p><p><code>PEventStore.find(...)</code> specifies the events that you want to read. In this case, &quot;user buy item&quot; events are read and then each is mapped to a <code>BuyEvent</code> object.</p><p><code>BuyEvent</code> case class is defined as:</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1</pre></td><td class="code"><pre><span class="k">case</span> <span class="k">class</span> <span class="nc">BuyEvent</span><span class="o">(</span><span class="n">user</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">item</span><span class="k">:</span> <span class="kt">String</span><span class="o">,</span> <span class="n">t</span><span class="k">:</span> <span class="kt">Long</span><span class="o">)</span>
</pre></td></tr></tbody></table> </div> <p><code>TrainingData</code> contains an RDD of <code>BuyEvent</code> objects. The class definition of <code>TrainingData</code> is:</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3</pre></td><td class="code"><pre><span class="k">class</span> <span class="nc">TrainingData</span><span class="o">(</span>
<span class="k">val</span> <span class="n">buyEvents</span><span class="k">:</span> <span class="kt">RDD</span><span class="o">[</span><span class="kt">BuyEvent</span><span class="o">]</span>
<span class="o">)</span> <span class="k">extends</span> <span class="nc">Serializable</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
</pre></td></tr></tbody></table> </div> <p>PredictionIO then passes the returned <code>TrainingData</code> object to <em>Data Preparator</em>.</p><div class="alert-message note"><p>You could modify the DataSource to read other event other than the default <strong>buy</strong>.</p></div><h3 id='data-preparator' class='header-anchors'>Data Preparator</h3><p>In MyComplementaryPurchase/src/main/scala/<strong><em>Preparator.scala</em></strong>, the <code>prepare</code> method of class <code>Preparator</code> takes <code>TrainingData</code> as its input and performs any necessary feature selection and data processing tasks. At the end, it returns <code>PreparedData</code> which should contain the data <em>Algorithm</em> needs.</p><p>By default, <code>prepare</code> simply copies the unprocessed <code>TrainingData</code> data to <code>PreparedData</code>:</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13</pre></td><td class="code"><pre><span class="k">class</span> <span class="nc">Preparator</span>
<span class="k">extends</span> <span class="nc">PPreparator</span><span class="o">[</span><span class="kt">TrainingData</span>, <span class="kt">PreparedData</span><span class="o">]</span> <span class="o">{</span>
<span class="nd">@transient</span> <span class="k">lazy</span> <span class="k">val</span> <span class="n">logger</span> <span class="k">=</span> <span class="nc">Logger</span><span class="o">[</span><span class="kt">this.</span><span class="k">type</span><span class="o">]</span>
<span class="k">def</span> <span class="n">prepare</span><span class="o">(</span><span class="n">sc</span><span class="k">:</span> <span class="kt">SparkContext</span><span class="o">,</span> <span class="n">td</span><span class="k">:</span> <span class="kt">TrainingData</span><span class="o">)</span><span class="k">:</span> <span class="kt">PreparedData</span> <span class="o">=</span> <span class="o">{</span>
<span class="k">new</span> <span class="nc">PreparedData</span><span class="o">(</span><span class="n">buyEvents</span> <span class="k">=</span> <span class="n">td</span><span class="o">.</span><span class="n">buyEvents</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">class</span> <span class="nc">PreparedData</span><span class="o">(</span>
<span class="k">val</span> <span class="n">buyEvents</span><span class="k">:</span> <span class="kt">RDD</span><span class="o">[</span><span class="kt">BuyEvent</span><span class="o">]</span>
<span class="o">)</span> <span class="k">extends</span> <span class="nc">Serializable</span>
</pre></td></tr></tbody></table> </div> <p>PredictionIO passes the returned <code>PreparedData</code> object to Algorithm&#39;s <code>train</code> function.</p><h2 id='algorithm' class='header-anchors'>Algorithm</h2><p>In MyComplementaryPurchase/src/main/scala/<strong><em>ALSAlgorithm.scala</em></strong>, the two methods of the algorithm class are <code>train</code> and <code>predict</code>. <code>train</code> is responsible for training the predictive model; <code>predict</code> is responsible for using this model to make prediction.</p><p>The default algorithm is based on concept of <a href="http://en.wikipedia.org/wiki/Association_rule_learning">Association Rule Learning</a> to find interesting association rules (A implies B) that indicates additional item (B) may be bought together given a list of items (A). A is the <em>condition</em> and B is the <em>consequence</em>.</p><h3 id='algorithm-parameters' class='header-anchors'>Algorithm parameters</h3><p>The Algorithm takes the following parameters, as defined by the <code>AlgorithmParams</code> case class:</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10</pre></td><td class="code"><pre><span class="k">case</span> <span class="k">class</span> <span class="nc">AlgorithmParams</span><span class="o">(</span>
<span class="n">basketWindow</span><span class="k">:</span> <span class="kt">Int</span><span class="o">,</span> <span class="c1">// in seconds
</span> <span class="n">maxRuleLength</span><span class="k">:</span> <span class="kt">Int</span><span class="o">,</span>
<span class="n">minSupport</span><span class="k">:</span> <span class="kt">Double</span><span class="o">,</span>
<span class="n">minConfidence</span><span class="k">:</span> <span class="kt">Double</span><span class="o">,</span>
<span class="n">minLift</span><span class="k">:</span> <span class="kt">Double</span><span class="o">,</span>
<span class="n">minBasketSize</span><span class="k">:</span> <span class="kt">Int</span><span class="o">,</span>
<span class="n">maxNumRulesPerCond</span><span class="k">:</span> <span class="kt">Int</span> <span class="c1">// max number of rules per condition
</span> <span class="o">)</span> <span class="k">extends</span> <span class="nc">Params</span>
</pre></td></tr></tbody></table> </div> <p>Parameter description:</p> <ul> <li><strong>basketWindow</strong>: The buy event is considered as the same basket as previous one if the time difference is within this window (in unit of seconds). For example, if it&#39;s set to 120, it means that if the user buys item B within 2 minutes of previous purchase (item A), then the item set [A, B] is considered as the same basket. The purchase of this <em>basket</em> is referred as one <em>transaction</em>.</li> <li><strong>maxRuleLength</strong>: The maximum length of the association rule length. Must be at least 2. For example, rule of &quot;A implies B&quot; has length of 2 while rule &quot;A, B implies C&quot; has a length of 3. Increasing this number will incrase the training time significantly because more combinations are considered.</li> <li><strong>minSupport</strong>: The minimum required <em>support</em> for the item set to be considered as rule (valid range is 0 to 1). It&#39;s the percentage of the item set appearing among all transcations. This is used to filter out infrequent item set. For example, setting to 0.1 means that the item set must appear in 10 % of all transactions.</li> <li><strong>minConfidence</strong>: The minimum <em>confidence</em> required for the rules (valid range is 0 to 1). The confidence indicates the probability of the condition and conseuquence appear in the same transaction. For example, if A appears in 30 transactions and the item set [A, B] appears in 20 transactions, then the rule &quot;A implies B&quot; has confidence of 0.66.</li> <li><strong>minLift</strong>: The minimum <em>lift</em> required for the rule. It should be set to 1 to find high quality rule. It&#39;s the confidence of the rule divided by the support of the consequence. It is used to filter out rules that the consequence is very frequent anyway regardless of the condition.</li> <li><strong>minBasketSize</strong>: The minimum number of items in basket to be considered by algorithm. This value must be at least 2.</li> <li><strong>maxNumRulesPerCond</strong>: Maximum number of rules generated per condition and stored in the model. By default, the top rules are sorted by <em>lift</em> score.</li> </ul> <div class="alert-message info"><p>If you import your own data and the engine doesn&#39;t return any results, it could be caused by the following reasons: (1) the algorithm parameter constraint is too high and the algo couldn&#39;t find rules that satisfy the condition. you could try setting the following param to 0: <strong>minSupport</strong>, <strong>minConfidence</strong>, <strong>minLift</strong> and then see if anything returned (regardless of recommendation quality), and then adjust the parameter accordingly. (2) the complementary purchase engine requires buy event with correct eventTime. If you import data without specifying eventTime, the SDK will use current time because it assumes the event happens in real time (which is not the case if you import as batch offline), resulting in that all buy events are treated as one big transcation while they should be treated as multiple transcations.</p></div><p>The values of these parameters can be specified in <em>algorithms</em> of MyComplementaryPurchase/<strong><em>engine.json</em></strong>:</p><div class="highlight shell"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18</pre></td><td class="code"><pre><span class="o">{</span>
...
<span class="s2">"algorithms"</span>: <span class="o">[</span>
<span class="o">{</span>
<span class="s2">"name"</span>: <span class="s2">"algo"</span>,
<span class="s2">"params"</span>: <span class="o">{</span>
<span class="s2">"basketWindow"</span> : 120,
<span class="s2">"maxRuleLength"</span> : 2,
<span class="s2">"minSupport"</span>: 0.1,
<span class="s2">"minConfidence"</span>: 0.6,
<span class="s2">"minLift"</span> : 1.0,
<span class="s2">"minBasketSize"</span> : 2,
<span class="s2">"maxNumRulesPerCond"</span>: 5
<span class="o">}</span>
<span class="o">}</span>
<span class="o">]</span>
...
<span class="o">}</span>
</pre></td></tr></tbody></table> </div> <p>PredictionIO will automatically loads these values into the constructor of the <code>Algorithm</code> class.</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4</pre></td><td class="code"><pre><span class="k">class</span> <span class="nc">Algorithm</span><span class="o">(</span><span class="k">val</span> <span class="n">ap</span><span class="k">:</span> <span class="kt">AlgorithmParams</span><span class="o">)</span>
<span class="k">extends</span> <span class="n">P2LAlgorithm</span><span class="o">[</span><span class="kt">PreparedData</span>, <span class="kt">Model</span>, <span class="kt">Query</span>, <span class="kt">PredictedResult</span><span class="o">]</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
</pre></td></tr></tbody></table> </div> <h3 id='train(...)' class='header-anchors'>train(...)</h3><p><code>train</code> is called when you run <strong>pio train</strong> to train a predictive model. The algorithm first find all basket transcations, generates and filters the association rules based on the algorithm parameters:</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38</pre></td><td class="code"><pre>
<span class="k">def</span> <span class="n">train</span><span class="o">(</span><span class="n">sc</span><span class="k">:</span> <span class="kt">SparkContext</span><span class="o">,</span> <span class="n">pd</span><span class="k">:</span> <span class="kt">PreparedData</span><span class="o">)</span><span class="k">:</span> <span class="kt">Model</span> <span class="o">=</span> <span class="o">{</span>
<span class="k">val</span> <span class="n">windowMillis</span> <span class="k">=</span> <span class="n">ap</span><span class="o">.</span><span class="n">basketWindow</span> <span class="o">*</span> <span class="mi">1000</span>
<span class="o">...</span>
<span class="k">val</span> <span class="n">transactions</span><span class="k">:</span> <span class="kt">RDD</span><span class="o">[</span><span class="kt">Set</span><span class="o">[</span><span class="kt">String</span><span class="o">]]</span> <span class="k">=</span> <span class="o">...</span>
<span class="k">val</span> <span class="n">totalTransaction</span> <span class="k">=</span> <span class="n">transactions</span><span class="o">.</span><span class="n">count</span><span class="o">()</span>
<span class="k">val</span> <span class="n">minSupportCount</span> <span class="k">=</span> <span class="n">ap</span><span class="o">.</span><span class="n">minSupport</span> <span class="o">*</span> <span class="n">totalTransaction</span>
<span class="o">...</span>
<span class="c1">// generate item sets
</span> <span class="k">val</span> <span class="n">itemSets</span><span class="k">:</span> <span class="kt">RDD</span><span class="o">[</span><span class="kt">Set</span><span class="o">[</span><span class="kt">String</span><span class="o">]]</span> <span class="k">=</span> <span class="n">transactions</span>
<span class="o">.</span><span class="n">flatMap</span> <span class="o">{</span> <span class="n">tran</span> <span class="k">=&gt;</span>
<span class="o">(</span><span class="mi">1</span> <span class="n">to</span> <span class="n">ap</span><span class="o">.</span><span class="n">maxRuleLength</span><span class="o">).</span><span class="n">flatMap</span><span class="o">(</span><span class="n">n</span> <span class="k">=&gt;</span> <span class="n">tran</span><span class="o">.</span><span class="n">subsets</span><span class="o">(</span><span class="n">n</span><span class="o">))</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="k">val</span> <span class="n">itemSetCount</span><span class="k">:</span> <span class="kt">RDD</span><span class="o">[(</span><span class="kt">Set</span><span class="o">[</span><span class="kt">String</span><span class="o">]</span>, <span class="kt">Int</span><span class="o">)]</span> <span class="k">=</span> <span class="o">...</span>
<span class="o">...</span>
<span class="k">val</span> <span class="n">rules</span><span class="k">:</span> <span class="kt">RDD</span><span class="o">[(</span><span class="kt">Set</span><span class="o">[</span><span class="kt">String</span><span class="o">]</span>, <span class="kt">RuleScore</span><span class="o">)]</span> <span class="k">=</span> <span class="o">...</span>
<span class="k">val</span> <span class="n">sortedRules</span> <span class="k">=</span> <span class="n">rules</span><span class="o">.</span><span class="n">groupByKey</span>
<span class="o">.</span><span class="n">mapValues</span><span class="o">(</span><span class="n">iter</span> <span class="k">=&gt;</span>
<span class="n">iter</span><span class="o">.</span><span class="n">toVector</span>
<span class="o">.</span><span class="n">sortBy</span><span class="o">(</span><span class="k">_</span><span class="o">.</span><span class="n">lift</span><span class="o">)(</span><span class="nc">Ordering</span><span class="o">.</span><span class="nc">Double</span><span class="o">.</span><span class="n">reverse</span><span class="o">)</span>
<span class="o">.</span><span class="n">take</span><span class="o">(</span><span class="n">ap</span><span class="o">.</span><span class="n">maxNumRulesPerCond</span><span class="o">)</span>
<span class="o">)</span>
<span class="o">.</span><span class="n">collectAsMap</span><span class="o">.</span><span class="n">toMap</span>
<span class="k">new</span> <span class="nc">Model</span><span class="o">(</span><span class="n">sortedRules</span><span class="o">)</span>
<span class="o">}</span>
</pre></td></tr></tbody></table> </div> <p>PredictionIO will automatically store the returned model after training, i.e. the <code>Model</code> object.</p><p>The <code>Model</code> stores the top rules for each condition:</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5</pre></td><td class="code"><pre><span class="k">class</span> <span class="nc">Model</span><span class="o">(</span>
<span class="k">val</span> <span class="n">rules</span><span class="k">:</span> <span class="kt">Map</span><span class="o">[</span><span class="kt">Set</span><span class="o">[</span><span class="kt">String</span><span class="o">]</span>, <span class="kt">Vector</span><span class="o">[</span><span class="kt">RuleScore</span><span class="o">]]</span>
<span class="o">)</span> <span class="k">extends</span> <span class="nc">Serializable</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
</pre></td></tr></tbody></table> </div> <h3 id='predict(...)' class='header-anchors'>predict(...)</h3><p><code>predict</code> is called when you send a JSON query to <a href="http://localhost:8000/queries.json">http://localhost:8000/queries.json</a>. PredictionIO converts the query, such as <code>{ &quot;items&quot; : [&quot;s2i1&quot;], &quot;num&quot; : 3 }</code> to the <code>Query</code> class you defined previously in <code>Engine.scala</code>.</p><p>The <code>predict()</code> function does the following:</p> <ol> <li>find all possible subset of the items in query</li> <li>use the subsets as condition to look up the model and return the rules for each condition.</li> </ol> <div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25</pre></td><td class="code"><pre>
<span class="o">...</span>
<span class="k">def</span> <span class="n">predict</span><span class="o">(</span><span class="n">model</span><span class="k">:</span> <span class="kt">Model</span><span class="o">,</span> <span class="n">query</span><span class="k">:</span> <span class="kt">Query</span><span class="o">)</span><span class="k">:</span> <span class="kt">PredictedResult</span> <span class="o">=</span> <span class="o">{</span>
<span class="k">val</span> <span class="n">conds</span> <span class="k">=</span> <span class="o">(</span><span class="mi">1</span> <span class="n">to</span> <span class="n">maxCondLength</span><span class="o">).</span><span class="n">flatMap</span><span class="o">(</span><span class="n">n</span> <span class="k">=&gt;</span> <span class="n">query</span><span class="o">.</span><span class="n">items</span><span class="o">.</span><span class="n">subsets</span><span class="o">(</span><span class="n">n</span><span class="o">))</span>
<span class="k">val</span> <span class="n">rules</span> <span class="k">=</span> <span class="n">conds</span><span class="o">.</span><span class="n">map</span> <span class="o">{</span> <span class="n">cond</span> <span class="k">=&gt;</span>
<span class="n">model</span><span class="o">.</span><span class="n">rules</span><span class="o">.</span><span class="n">get</span><span class="o">(</span><span class="n">cond</span><span class="o">).</span><span class="n">map</span><span class="o">{</span> <span class="n">vec</span> <span class="k">=&gt;</span>
<span class="k">val</span> <span class="n">itemScores</span> <span class="k">=</span> <span class="n">vec</span><span class="o">.</span><span class="n">take</span><span class="o">(</span><span class="n">query</span><span class="o">.</span><span class="n">num</span><span class="o">).</span><span class="n">map</span> <span class="o">{</span> <span class="n">rs</span> <span class="k">=&gt;</span>
<span class="k">new</span> <span class="nc">ItemScore</span><span class="o">(</span>
<span class="n">item</span> <span class="k">=</span> <span class="n">rs</span><span class="o">.</span><span class="n">conseq</span><span class="o">,</span>
<span class="n">support</span> <span class="k">=</span> <span class="n">rs</span><span class="o">.</span><span class="n">support</span><span class="o">,</span>
<span class="n">confidence</span> <span class="k">=</span> <span class="n">rs</span><span class="o">.</span><span class="n">confidence</span><span class="o">,</span>
<span class="n">lift</span> <span class="k">=</span> <span class="n">rs</span><span class="o">.</span><span class="n">lift</span>
<span class="o">)</span>
<span class="o">}.</span><span class="n">toArray</span>
<span class="nc">Rule</span><span class="o">(</span><span class="n">cond</span> <span class="k">=</span> <span class="n">cond</span><span class="o">,</span> <span class="n">itemScores</span> <span class="k">=</span> <span class="n">itemScores</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">}.</span><span class="n">flatten</span><span class="o">.</span><span class="n">toArray</span>
<span class="k">new</span> <span class="nc">PredictedResult</span><span class="o">(</span><span class="n">rules</span><span class="o">)</span>
<span class="o">}</span>
<span class="o">...</span>
</pre></td></tr></tbody></table> </div> <p>PredictionIO passes the returned <code>PredictedResult</code> object to <em>Serving</em>.</p><h2 id='serving' class='header-anchors'>Serving</h2><p>The <code>serve</code> method of class <code>Serving</code> processes predicted result. It is also responsible for combining multiple predicted results into one if you have more than one predictive model. <em>Serving</em> then returns the final predicted result. PredictionIO will convert it to a JSON response automatically.</p><p>In MyComplementaryPurchase/src/main/scala/<strong><em>Serving.scala</em></strong>,</p><div class="highlight scala"><table style="border-spacing: 0"><tbody><tr><td class="gutter gl" style="text-align: right"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11</pre></td><td class="code"><pre><span class="k">class</span> <span class="nc">Serving</span>
<span class="k">extends</span> <span class="nc">LServing</span><span class="o">[</span><span class="kt">Query</span>, <span class="kt">PredictedResult</span><span class="o">]</span> <span class="o">{</span>
<span class="nd">@transient</span> <span class="k">lazy</span> <span class="k">val</span> <span class="n">logger</span> <span class="k">=</span> <span class="nc">Logger</span><span class="o">[</span><span class="kt">this.</span><span class="k">type</span><span class="o">]</span>
<span class="k">override</span>
<span class="k">def</span> <span class="n">serve</span><span class="o">(</span><span class="n">query</span><span class="k">:</span> <span class="kt">Query</span><span class="o">,</span>
<span class="n">predictedResults</span><span class="k">:</span> <span class="kt">Seq</span><span class="o">[</span><span class="kt">PredictedResult</span><span class="o">])</span><span class="k">:</span> <span class="kt">PredictedResult</span> <span class="o">=</span> <span class="o">{</span>
<span class="n">predictedResults</span><span class="o">.</span><span class="n">head</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></td></tr></tbody></table> </div> <p>When you send a JSON query to <a href="http://localhost:8000/queries.json">http://localhost:8000/queries.json</a>, <code>PredictedResult</code> from all models will be passed to <code>serve</code> as a sequence, i.e. <code>Seq[PredictedResult]</code>.</p><div class="alert-message note"><p>An engine can train multiple models if you specify more than one Algorithm component in <code>object ComplementaryPurchaseEngine</code> inside <strong><em>Engine.scala</em></strong> and the corresponding parameters in <strong><em>engine.json</em></strong>. Since only one <code>Algorithm</code> is implemented by default, this <code>Seq</code> contains one element.</p></div></div></div></div></div><footer><div class="container"><div class="seperator"></div><div class="row"><div class="col-md-6 col-xs-6 footer-link-column"><div class="footer-link-column-row"><h4>Community</h4><ul><li><a href="//docs.prediction.io/install/" target="blank">Download</a></li><li><a href="//docs.prediction.io/" target="blank">Docs</a></li><li><a href="//github.com/apache/incubator-predictionio" target="blank">GitHub</a></li><li><a href="mailto:user-subscribe@predictionio.incubator.apache.org" target="blank">Subscribe to User Mailing List</a></li><li><a href="//stackoverflow.com/questions/tagged/predictionio" target="blank">Stackoverflow</a></li></ul></div></div><div class="col-md-6 col-xs-6 footer-link-column"><div class="footer-link-column-row"><h4>Contribute</h4><ul><li><a href="//predictionio.incubator.apache.org/community/contribute-code/" target="blank">Contribute</a></li><li><a href="//github.com/apache/incubator-predictionio" target="blank">Source Code</a></li><li><a href="//issues.apache.org/jira/browse/PIO" target="blank">Bug Tracker</a></li><li><a href="mailto:dev-subscribe@predictionio.incubator.apache.org" target="blank">Subscribe to Development Mailing List</a></li></ul></div></div></div></div><div id="footer-bottom"><div class="container"><div class="row"><div class="col-md-12"><div id="footer-logo-wrapper"><img alt="PredictionIO" src="/images/logos/logo-white-d1e9c6e6.png"/></div><div id="social-icons-wrapper"><a class="github-button" href="https://github.com/apache/incubator-predictionio" data-style="mega" data-count-href="/apache/incubator-predictionio/stargazers" data-count-api="/repos/apache/incubator-predictionio#stargazers_count" data-count-aria-label="# stargazers on GitHub" aria-label="Star apache/incubator-predictionio on GitHub">Star</a> <a class="github-button" href="https://github.com/apache/incubator-predictionio/fork" data-icon="octicon-git-branch" data-style="mega" data-count-href="/apache/incubator-predictionio/network" data-count-api="/repos/apache/incubator-predictionio#forks_count" data-count-aria-label="# forks on GitHub" aria-label="Fork apache/incubator-predictionio on GitHub">Fork</a> <script id="github-bjs" async="" defer="" src="https://buttons.github.io/buttons.js"></script><a href="//www.facebook.com/predictionio" target="blank"><img alt="PredictionIO on Twitter" src="/images/icons/twitter-ea9dc152.png"/></a> <a href="//twitter.com/predictionio" target="blank"><img alt="PredictionIO on Facebook" src="/images/icons/facebook-5c57939c.png"/></a> </div></div></div></div></div></footer></div><script>(function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){
(w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t);
e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.insertBefore(s,e);
})(window,document,'script','//s.swiftypecdn.com/install/v1/st.js','_st');
_st('install','HaUfpXXV87xoB_zzCQ45');</script><script src="/javascripts/application-5a24945b.js"></script></body></html>