arduino2 post:x13077444 title x13077444 body Wikimedia – DcK Area https://www.dereckson.be/blog Chaos, entropy, communities, code and some other things. Sun, 06 Mar 2022 13:47:35 +0000 en-US hourly 1 https://wordpress.org/?v=6.2.5 https://www.dereckson.be/blog/wp-content/uploads/2018/03/225-100x100.jpg Wikimedia – DcK Area https://www.dereckson.be/blog 32 32 Get URL from Git commit hash https://www.dereckson.be/blog/2022/03/06/get-url-from-git-commit-hash/ https://www.dereckson.be/blog/2022/03/06/get-url-from-git-commit-hash/#respond Sun, 06 Mar 2022 13:47:34 +0000 https://www.dereckson.be/blog/?p=851 SHA-1 Git hashes can be mapped to code review or code repository URL to offer a web visualization with additional context.

The resolve-hash command allows to get such URL from a Git hash, or another VCS reference. It can search Phabricator, Gerrit, GitHub and GitLab currently.

Ouf of the box, it will detect your ~/.arcrc configuration and use GitHub public API. You can create a small YAML configuration file it to add Gerrit and GitLab in the mix.

Install it. Use it.

The resolve-hash package is available on PyPI.

$ pip install resolve-hash

$ resolve-hash 6411f75775a7aa8db
https::⃫github.com/10up/simple-local-avatars/commit/6411f75775a7aa8db2ef097d70b12926018402c1

Specific use cases

Projects moved from GitHub to GitLab

GitLab requires any query to the search API to be authenticated. You can generate a personal access token in your user settings; the API scope is enough, so check only read_api.

Then you can add create a $HOME/.config/resolve-hash.conf file with the following content:

# GitLab
gitlab_public_token: glpat-sometoken

For Wikimedia contributors

Gerrit exposes a REST API. To use it, create a $HOME/.config/resolve-hash.conf file with the following content:

# Gerrit REST API
gerrit:
  - https://gerrit.wikimedia.org/r/

Gerrit will be then queried before GitHub:

$ resolve-hash 311d17f289470
https::⃫gerrit.wikimedia.org/r/c/mediawiki/core/+/768149

Note if you’ve configured Arcanist to interact with phabricator.wikimedia.org, your configuration in ~/.arcrc is used BEFORE the Gerrit one. Tell me if you’re in that case, we’ll allow to order resolution strategies.

What inspired this project?

Terminator allows plugins to improve the behavior of the terminal. Some plugins allows to expressions like Bug:1234 to offer a link to the relevant bug tracker.

What if we can detect hashes, especially VCS hashes, to offer a link to the code review system, like Phabricator or Gerrit, or at least to a public code hosting facility like GitHub?

What’s next?

We can add support for private instances of GitHub Enterprise and GitLab. Code I wrote in VCS package is already ready to accept any GitHub or GitLab URL, and is so prepared to accept a specific instance, so it’s a matter of declare new configuration options and add the wrapper code in VcsHashSearch class.

A cache would be useful to speed up the process. Hashes are stable enough for that.

Write a Terminator plugin, so we solve the root problem described above.

The code is extensible enough to search other kind of hashes than commits, but I’m not sure we’ve reliable sources of hashes for know files or packages.

References

]]>
https://www.dereckson.be/blog/2022/03/06/get-url-from-git-commit-hash/feed/ 0
One year of contributions to Wikimedia — 2016 https://www.dereckson.be/blog/2017/01/31/one-year-of-contributions-to-wikimedia-2016/ https://www.dereckson.be/blog/2017/01/31/one-year-of-contributions-to-wikimedia-2016/#respond Tue, 31 Jan 2017 01:23:00 +0000 https://www.dereckson.be/blog/?p=604 Some statistics I’ve computed about my production contributions to Wikimedia:

342 actions logged on the server admin log
573 commits to Wikimedia repos, of which:
 5  new wikis created (hello tcy.wikipedia.org)

Thanks to all the people whom I’ve met or I’ve been engaged with during this year for these contributions.

]]>
https://www.dereckson.be/blog/2017/01/31/one-year-of-contributions-to-wikimedia-2016/feed/ 0
MediaWiki now accepts out of the box RDFa and Microdata semantic markup https://www.dereckson.be/blog/2016/03/18/mediawiki-now-accepts-out-of-the-box-rdfa-and-microdata-semantic-markup/ https://www.dereckson.be/blog/2016/03/18/mediawiki-now-accepts-out-of-the-box-rdfa-and-microdata-semantic-markup/#respond Fri, 18 Mar 2016 19:17:46 +0000 https://www.dereckson.be/blog/?p=545 Semantic web

Since MediaWiki 1.16, the software has supported — as an option — RDFa and Microdata HTML semantic attributes.

This commit, integrated to the next release on MediaWiki, 1.27, will embrace more the semantic Web making these attributes always available.

If you wish to use it today, this is already available in our Git repository.

This also simplify slightly the cyclomatic complexity of our parser sanitizer code.

Microdata support will so be available on Wikipedia Thursday, 24 March 2016 and on other projects Thursday, 23 March 2016.

If you already use RDFa today on MediaWiki

First, we would be happy to get feedback, as we’re currently considering an update to RDFa  1.1 and we would like to know who is still in favour to keep RDFa 1.0.

Secondly, there is a small effort of configuration to do: open the source code of your wiki and look the <html> tag.

Copy the content of the version attribute: you should see something like like <html version=HTML+RDFa 1.0">.

Now, edit InitialiseSettings.php (or your wiki farm configuration) and set the $wgHtml5Version setting. For example here, this would be:
$wgHtml5Version="=HTML+RDFa 1.0";

For the microdata, there is nothing special to do.

 

]]>
https://www.dereckson.be/blog/2016/03/18/mediawiki-now-accepts-out-of-the-box-rdfa-and-microdata-semantic-markup/feed/ 0
December 2014 links https://www.dereckson.be/blog/2015/01/01/december-2014-links/ https://www.dereckson.be/blog/2015/01/01/december-2014-links/#comments Thu, 01 Jan 2015 07:40:41 +0000 http://www.dereckson.be/blog/?p=426 Some links of stuff I appreciated this month. Links to French content are in a separate post. You can also take the time machine to November 2014.

AI

What if instead to understand how the brain works, we copy the neural connections as is? This is what the OpenWorm project tries to do with C. elegans. And, big surprise, that works and allows a bot to move.

Wikipedia

An infographics of the locality of Wikipedia participants shows without any surprise they are mainly from Europe and North America.

If you’re into dumps, the Wikipedia / MediaWiki XML dump grepper will help you to find a particular piece of data, like the text of one article.

Tools

Dev / search. The silver searcher, ag, offers a faster approach than ack to search your code.

Fun / autogenerator. Some years ago, cgMusic offered an implementation on how a computer program could create music. Add some image generation techniques and a word generators, and you can have a fake music generator offering full albums. Ælfgar has stumbled upon Liquified Death by Income Yield.

GIS. Turf is a new open source JavaScript GIS library. This post explains the capabilities and features, including its great offline support.

Electronics

What if an Arduino embeds a web server and allows programmation from the web browser? This is exactly what the Photon by Spark does.

Quartz

An infographics showing satellites orbiting Earth and a point of view of the Uber economy.

Literature

The GoT series offer some comprehensive scenes of torture. Did you ask yourself their interest or need for the plot? Marie Brennan offers a great opinion in « Welcome to the Desert of the Real ».

]]>
https://www.dereckson.be/blog/2015/01/01/december-2014-links/feed/ 1
November 2014 links https://www.dereckson.be/blog/2014/12/01/november-2014-links/ https://www.dereckson.be/blog/2014/12/01/november-2014-links/#respond Mon, 01 Dec 2014 07:45:49 +0000 http://www.dereckson.be/blog/?p=388 Some links of stuff I appreciated this month. Links to French content are in a separate post. You can also take the time machine to October 2014.

November is the Philae landing on the Comet Churyumov-Gerasimenko month and the ESA photo release under CC-BY-SA (one of them here) month. Mainly DevOps links in this post, a Wikidata tool and an algorithm visualisation.

Churyumov-Gerasimenko 67P, 20 November 2014
ESA/Rosetta/NAVCAM, CC-BY-SA 3.0 IGO

Dev

Craft. Jeroen de Dauw has prepared interesting slides about clean functions. Your function should do one task, not be a class disguised in procedural code.

Raft. In a distributed environment, how do you achieve a similar state? Raft is an answer to this question, as a distributed consensus algorithm.  To understand how it works, The Secret Lives of Data offers a visual guide.

Wikidata

Wikidata no labels. Harmonia Amanda and Hsarrazin wanted to find items without labels in French, respectively about the Tolkien’s Legendarium or Russians persons to translate. This tool allows you to get some Wikidata items through a WDQ query or to encode them directly, and print a table with the part of these items without label in the specified language.

DevOps

Once upon a time there were a Linux theme park. As a Cobbler / SpaceWalk alternative, we start to see new software to appear: katello/foreman. It’s a part of Katello, the upstream of Satellite 6, and a replacement for SpaceWalk. You want to dive into the Linux theme park? Build images, deploy, manage resources? You’ll be served. Thank you to jnix for these software recommendation.

And now, near the sea. ShipYard allows you to manage Docker instances and containers.

But what is more interesting is the alpha release of OpenShift Origin, the third generation of  OpenShift, with a new system design. It relies on Docker and the following technologies:

  • Kubernetes, an active controller to orchestrate and ensure the desired state of the containers;
  • An etcd server (which uses the Raft algorithm described above);

With that concepts, you’re ready for the introduction hands-on tutorial available.

The puppetmaster becomes old. Ryan Lane, formerly in Wikimedia ops team,  blogged this summer about a Puppet alternative at his new job: Moving away from Puppet: SaltStack or Ansible? For Ryan, 10K+ lines of Puppet codes is now only 1K of SaltStack or Ansible code. The winner of their test to port the Puppet infrastructure into both is SaltStack. It’s a pity, I would have loved to merge yet another fictional universe into the Nasqueron project and add the Ursula K. Guin ansible in the mix.

Sysadmin

FreeBSD 10.1. The first new version of FreeBSD after the SSL bugs is out, and will immediately be deployed on Ysul and Sirius machines as test. Bhyve can use a pure ZFS filesystem and UDP-Lite protocol is finally here.

]]>
https://www.dereckson.be/blog/2014/12/01/november-2014-links/feed/ 0
October 2014 links https://www.dereckson.be/blog/2014/10/31/october-2014-links/ https://www.dereckson.be/blog/2014/10/31/october-2014-links/#comments Fri, 31 Oct 2014 07:31:02 +0000 http://www.dereckson.be/blog/?p=357 Some links of stuff I appreciated this month. Links to French content are in a separate post.

In the servers world

SSL. October is the month we disabled SSLv3 protocol support from nginx following the POODLE attack. So this means we can look to this paper, nginx configuration and a tool to check SSL configuration. The provider Linode has published a comprehensive guide to mitigate the attack.

FreeBSD. FreeBSD 10.1-RELEASE will soon be available. The virtual terminal console driver vt is improved. Oh, and you can now boot bhybe on ZFS. Shell servers will have to deal with the fact login.conf settings will take precedence on .profile and other shell environment for variables like path, blocksize or umask.

Docker. To improve Docker workflow, nitrous.io has released tug, a set of scripts in Go to help common tasks.

Thus shall ye compile in JavaScript

Humble Bundle launches the Humble Mozilla Bundle, games compiled in ASM.js and so playable in the browser.

Meanwhile, in the functionnal language world, a paper shows you can compile OCaml in JS, an it’s sometimes quicker in the JS JIT than it its own JIT (but well… you can also compile OCaml in native, and OCaml JIT isn’t really well optimized).

So if you want to respect this commandment, just compile your C code with clang: emscripten will then happily compile your LLVM bytecode in ASM.js.

Gamergate / NotYourShield

A CNN journalist reads the gamergate as the end of the narration controlled by journalists.

When an Examiner journalist suggests #NotYourShield is 4chan white heterosexual users posing as women and PoC, his tweet is replied with a lot of photos from women and PoC. We so now have a picture of the diversity in video games (permanent link).

On a related theme, I Can Tolerate Anything Except The Outgroup is interesting to read and heavily commented.

Finally, a call for help:

Curiosities

Some scientists push to a new definition of planet, to take in account exoplanets. In such a definition, Pluto would be again a planet. Harvard organized a debate, this position wins.

At Databricks, they carved this pumpkin for halloween:

]]>
https://www.dereckson.be/blog/2014/10/31/october-2014-links/feed/ 2
126 473 https://www.dereckson.be/blog/2013/11/20/126-473/ https://www.dereckson.be/blog/2013/11/20/126-473/#respond Wed, 20 Nov 2013 13:24:52 +0000 http://www.dereckson.be/blog/?p=274 126 473 …

… this is the amount of English wikipedia contributors allowed to participate to the 2013 2013 Arbitration Committee Elections.

The eligibility condition checked by this number is the amount of accounts having made at least 150 mainspace edits by 1 November 2013.

The English Wikipedia has now 12 years. This figure so means there is something like ten thousands contributors having made a significant contribution to the main namespace each year, a little less than 1 000 per month,  at least one per hour.

If the English Wikipedia would be a country, and those people its population,  it would the 192th according this list of countries by population. This is something between Jersey or the United States Virgin Islands, and Guam.

Thank you to Ælfgar for this country comparison idea.

]]>
https://www.dereckson.be/blog/2013/11/20/126-473/feed/ 0
MediaWiki nginx configuration file https://www.dereckson.be/blog/2013/10/24/mediawiki-nginx-configuration-file/ https://www.dereckson.be/blog/2013/10/24/mediawiki-nginx-configuration-file/#respond Thu, 24 Oct 2013 19:07:08 +0000 http://www.dereckson.be/blog/?p=257 Scenario

  • You have a nginx webserver
  • You have several MediaWiki installation on this server
  • You would like to have a simple and clear configuration

Solution

You want a configuration file you can include in every server {} block MediaWiki is available

Implementation

  1. Create a includes subdirectory in your nginx configuration directory (by default, /usr/local/etc/nginx or /etc/nginx).
    This directory can welcome every configuration block you don’t want to repeat in each server block.
  2. You put in this directory mediawiki-root.conf, mediawiki-wiki.conf or your own configuration block.
  3. In each server block, you can now add the following line:
    Include includes/mediawiki-root.conf;

Configuration I – MediaWiki in the root web directory, /article path

This is mediawiki-root.conf on my server:

        # Common settings for a wiki powered by MediaWiki with the following configuration:
        #   (1) MediaWiki is installed in $root folder
        #   (2) Article path is /&lt;title&gt;
        #   (3) LocalSettings.php contains $wgArticlePath = "/$1"; $wgUsePathInfo = true;

        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }

        location ~ ^/images/thumb/(archive/)?[0-9a-f]/[0-9a-f][0-9a-f]/([^/]+)/([0-9]+)px-.*$ {
            #Note: this doesn't work with InstantCommons.
            if (!-f $request_filename) {
                rewrite ^/images/thumb/[0-9a-f]/[0-9a-f][0-9a-f]/([^/]+)/([0-9]+)px-.*$ /thumb.php?f=$1&amp;width=$2;
                rewrite ^/images/thumb/archive/[0-9a-f]/[0-9a-f][0-9a-f]/([^/]+)/([0-9]+)px-.*$ /thumb.php?f=$1&amp;width=$2&amp;archived=1;
            }
        }

        location /images/deleted    { deny all; }
        location /cache             { deny all; }
        location /languages         { deny all; }
        location /maintenance       { deny all; }
        location /serialized        { deny all; }
        location ~ /.(svn|git)(/|$) { deny all; }
        location ~ /.ht             { deny all; }
        location /mw-config         { deny all; }

Configuration II – MediaWiki in the /w directory, /wiki/article path

This is mediawiki-wiki.conf on my server:

        # Common settings for a wiki powered by MediaWiki with the following configuration:
        #   (1) MediaWiki is installed in $root/w folder
        #   (2) Article path is /wiki/&lt;title&gt;
        #   (3) LocalSettings.php contains $wgArticlePath = "/wiki/$1"; $wgUsePathInfo = true;

        location /wiki {
            try_files $uri $uri/ /w/index.php?$query_string;
        }

        location ~ ^/w/images/thumb/(archive/)?[0-9a-f]/[0-9a-f][0-9a-f]/([^/]+)/([0-9]+)px-.*$ {
            #Note: this doesn't work with InstantCommons.
            if (!-f $request_filename) {
                rewrite ^/w/images/thumb/[0-9a-f]/[0-9a-f][0-9a-f]/([^/]+)/([0-9]+)px-.*$ /w/thumb.php?f=$1&amp;width=$2;
                rewrite ^/w/images/thumb/archive/[0-9a-f]/[0-9a-f][0-9a-f]/([^/]+)/([0-9]+)px-.*$ /w/thumb.php?f=$1&amp;width=$2&amp;archived=1;
            }
        }

        location /w/images/deleted  { deny all; }
        location /w/cache           { deny all; }
        location /w/languages       { deny all; }
        location /w/maintenance     { deny all; }
        location /w/serialized      { deny all; }
        location ~ /.(svn|git)(/|$) { deny all; }
        location ~ /.ht             { deny all; }
        location /w/mw-config       { deny all; }

Example of use

www.wolfplex.org serves other application is subdirectories and MediaWiki for /wiki URLs.

This server block:

  1. is a regular one
  2. includes our includes/mediawiki-wiki.conf configuration file (scenario II)
  3. contains a regular php-fpm block
  4. contains other instructions

    server {
        listen          80;
        server_name     www.wolfplex.org

        access_log      /var/log/www/wolfplex.org/www-access.log main;
        error_log       /var/log/www/wolfplex.org/www-error.log;
        root            /var/wwwroot/wolfplex.org/www;
        index           index.html index.php index.htm;

        [...]

        include         includes/mediawiki-wiki.conf;

        location / {
            #Link to the most relevant page to present the project
            rewrite /presentation/?$ /w/index.php?title=Presentation last;

            #Link to the most relevant page for bulletin/news information:
            rewrite /b/?$ /w/index.php?title=Bulletin:Main last;

            [...]
        }

        [...]

        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_pass   127.0.0.1:9010;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }
    }

Some notes

  • Configuration is based on Daniel Friesen’s , who collected various working nginx ones. There are some differences in the rewrite, our goal here is to have a generic configuration totally agnostic of the way .php files are handled.
  • Our configuration (not the one generated by the builder) uses a if for the thumbnails handler. The nginx culture is a culture where you should try something else than an if. See this nginx wiki page and this post about the location if way of work for more information.
]]>
https://www.dereckson.be/blog/2013/10/24/mediawiki-nginx-configuration-file/feed/ 0
8 bit music in pure JavaScript https://www.dereckson.be/blog/2013/08/24/8-bit-music-in-javascript/ https://www.dereckson.be/blog/2013/08/24/8-bit-music-in-javascript/#respond Sat, 24 Aug 2013 14:52:08 +0000 http://www.dereckson.be/blog/?p=218 HTML 5 offers a Web Audio API, to add synthesizing audio support in web applications.

Cody Lundquist, an Australian from Sidney, created a 8 bit music audio library built on the top of the Web Audio API, called 8Bit.js Audio Library.

You define a time (e.g. 4/4), a tempo, and you then the notes.

Submitted 2 days on Reddit, the library got a favorable reception, with some people adapting themes. There is even an original composition, rather nice, called Cities.

A LilyPond support is planned, so in the future there could be a possibility to implement this library into the MediaWiki score extension.

Not yet for every browser

  • Safari 6 supports it, so only on iOS and Mac, not yet on Windows.
  • Chrome 10+ supports it, and so Opera 15,
  • Firefox, Internet Explorer and Opera 12 don’t support it.
    [2020 edit: Firefox has added support for it in versions 25 (desktop) and 26 (mobile)]

Links

Acknowledgment

Thanks to Linedwell for the help during browsers test.

]]>
https://www.dereckson.be/blog/2013/08/24/8-bit-music-in-javascript/feed/ 0
Gerrit activity feeds :: a design and infrastructure sneak peak https://www.dereckson.be/blog/2013/01/20/gerrit-activity-feeds-a-design-and-infrastructure-sneak-peak/ https://www.dereckson.be/blog/2013/01/20/gerrit-activity-feeds-a-design-and-infrastructure-sneak-peak/#comments Sun, 20 Jan 2013 11:11:30 +0000 http://www.dereckson.be/blog/?p=195 Gerrit provides nice views by changes, but doesn’t offer synthetic and consolidated views.

Activity feeds will be timelines to offer these views;

  • What are the users’ last activities (commits, patchsets, merges) on Gerrit?
  • What’s going on on the mediawiki/extensions/SemanticMediaWiki repository?

Here the homepage dashboard:

GerritActivyFeeds

And here the wireframe of the project activity feed:

ProjectActivityFeedWireframe

About the design

This code is built on the top of Foundation, a responsive CSS framework. This allows to provide a smooth experience for your phone or tablet: columns will collapse into a more linear view if resolution width is narrow.

Avatars uses Gravatar. When an user doesn’t have a Gravatar account, identicons are used.

About the infrastructure and code

A Node service acts as proxy, and mirrors the Gerrit events stream, so it’s available to any simple TCP connexion instead to require a SSH connection.

I’ll provide access to this Node server to the community, so any tool with socket and JSON support with be able to interact with Gerrit events. If you’ve a need for a push model, ie to post notifications, please let me know the format and I will take care of that.

Then, a script reads the stream and write the XML feeds. It also monitors the Node -> SSH connection, to relaunch the service if needed (e.g. if the Jenkins server is rebooted). These XML feeds are publicly accessible, so you can also create a service based on them.

Finally, XSLT will be used to render these feeds in HTML and RSS documents. That’s for the humans and the most generic tools..

It will be at this moment time to take care of special needs, like combined feeds for Google Summer of Code or the Outreach Program for Women.

What do you think of this and do you need?

Please tell me what you think of this tool, and what you would like to find on this tool.

We also need a cool name for the application.

]]>
https://www.dereckson.be/blog/2013/01/20/gerrit-activity-feeds-a-design-and-infrastructure-sneak-peak/feed/ 1
post:x13077365 title x13077365 body Deeper Analytics - Last entrieshttp://deeperanalytics.be/blog/The last entries on the site Deeper Analyticsen-usZinniaMon, 22 Apr 2019 05:26:53 +0000Building a wifi based IoT device for home automation http://deeperanalytics.be/blog/2018/04/11/building-wifi-based-iot-device-home-automation/<p>In this blog post, we will build an Internet Of Thing (IoT) device based on the super cheap ESP8266 chip.  The device is used to automate home shutters at a predefined time of the day or according to the house temperature in order to limit the temperature increase caused by sunlight.</p> <p style="text-align:justify">In this blog post, we will build an Internet Of Thing (IoT) device based on the super cheap <a href="https://en.wikipedia.org/wiki/ESP8266">ESP8266 </a>chip.&nbsp; The device is used to automate home shutters at a predefined time of the day or according to the house temperature in order to limit the temperature increase caused by sunlight.&nbsp;The ESP8266 chip is a microcontroller with WiFi capabilities and a full TCP/IP stack.&nbsp; We can, therefore, have a complete web server and REST API running in it so as an MQTT client that can be used to send commands to the chip from anywhere and also to receive data from the chip.&nbsp; The project was split into phases:</p> <h3 style="text-align:justify"><strong>1) Desired capabilities:</strong></h3> <p style="text-align:justify">The requirements for this device are:</p> <ul> <li style="text-align:justify"><strong>Cheap</strong>:&nbsp; We have ~10 shutters to control so it has to be cheap and for sure cheaper than equivalent commercial devices.&nbsp;&nbsp;</li> <li style="text-align:justify"><strong>Grid-Powered</strong>: It has to be connected to the power plug as&nbsp;we don&#39;t want to change the battery of every single device yearly.</li> <li style="text-align:justify"><strong>Small</strong>: We want it to be located inside the wall of the house in the cavity behind the manual shutter switch (that we are replacing&nbsp;by this&nbsp;device).&nbsp; The footprint should be around 50x40mm with a depth of less than 25mm.</li> <li style="text-align:justify"><strong>Connected</strong>:&nbsp; It should onboard a webserver, a WiFi antena,&nbsp;and be capable of acting simultaniously as a wifi station or as an access point.</li> <li style="text-align:justify"><strong>Over The Air (OTA) Programming</strong>:&nbsp; We should be able to update the software of the device remotely.&nbsp; The device will be hardly accessible once placed in the wall.</li> <li style="text-align:justify"><strong>Control</strong>:&nbsp; The device should be able to roll-down and to roll-up the shutter.&nbsp; It should therefore be able to open/close 220V highly inductive circuits.</li> <li style="text-align:justify"><strong>Easy</strong>:&nbsp; It should be easy to build and to program, and it has to be well docummented and widely supported by the community.</li> </ul> <h3 style="text-align:justify"><strong>2) Designing:</strong></h3> <p style="text-align:justify">The minimal pieces that we need to put together in order to meet these requirements are:</p> <ul> <li style="text-align:justify">Connected, Cheap, Easy, Small, and OTA programming: The ESP8266 chip is the ideal candidate for this, it just cost a few dollars, and there are tons of tutorial and documentation all over the web.&nbsp; There are many module version integrating this chip, we opted for the ESP-12E version whih has good WiFi range performance and quite enough GPIO to control our shutters and other stuff like temperature sensors.&nbsp; The ESP8266 module requires a 3.3V power supply delivering 500mA (necessary during WiFi communication).&nbsp;</li> <li style="text-align:justify">Conrol and Small: In order to control the shutters we basically have two options, either we use an electrical relay which is cheap but big, noisy, slow and with a limited lifetime or we use (opto-isolated) triacs that are expensive but have the advantage to be small, silent and robust.&nbsp; We chose to use the L4008D6 triacs which can handle 8 Amps in 400VAC and&nbsp;are snubberless which is quite necessary given the high inductance of the shutter motors.&nbsp; The two triacs are controlled by&nbsp;two&nbsp;MOC3081M optocouplers which also add an insulation between the high voltage (220V AC) circuit and the low voltage (3.3V DC) circuit.</li> <li style="text-align:justify">Grid-Powered, Cheap and&nbsp;Small:&nbsp;We need a small footprint power supply that can delive the 3.3VDC required by the ESP8266 from the 220VAC wall plug.&nbsp; Usual passive transformers are excluded has those beast are rather big and expensive.&nbsp; So the only real alternative here are the &quot;made in china&quot; switching power supply that are quite often used in mobile phone chargers.&nbsp; Note that these devices should be used with a lot of care as they handle pretty high voltages (&gt;KV) and load some large capacitors...&nbsp; this is quite a deadly mix so it should really be manipulated with caution.</li> </ul> <p style="text-align:justify">In order to minimize the size of the entire device, we have no other choice than smartly designing the Printed Circuit Board (PCB) of the device to combine all these elements on a minimal space.&nbsp; Note:&nbsp; our final design uses a double sided PCB which have the switching power supply located behind the ESP8266 wifi antena.&nbsp; This is far from idea has&nbsp;the switching power supply could cause interferences that negatively affect the wifi antena.&nbsp; We will therefore need to make an initial prototype to make sure that this location is acceptable.&nbsp; We have also added a small temperature sensor on the board to monitor the Triacs temperature in the testing phase of the board.&nbsp; Note that the proximity of the temperature sensor&nbsp;to the power supply makes it totally unusable to measure the house temperature (there is an obvious&nbsp;bias of 4-5&deg;C).</p> <p style="text-align:justify">See below the schema&nbsp;for this&nbsp;device:</p> <p style="text-align:justify">&nbsp;</p> <div class="row"> <div class="center-block col-xs-12"><img alt="" src="/uploads/uploads/ckeditor/2018/04/11/Schema1.PNG" style="width:80%" /></div> </div> <div class="row"> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2018/04/11/Schema2.PNG" style="width:80%" /></p> </div> <p>See below the board design for this&nbsp;device:</p> <div class="row"> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2018/04/11/board.jpg" style="width:80%" /></p> </div> <p style="text-align:center">&nbsp;</p> <p style="text-align:center">&nbsp;</p> <p style="text-align:justify">We can now compute what would be the cost of the device based on the component list and price for small quantities:</p> <table align="center" border="0" cellpadding="1" cellspacing="1" style="width:100%"> <thead> <tr> <th scope="col" style="text-align:justify"><strong>Component</strong></th> <th scope="col" style="text-align:justify"><strong>Unit Price ($)</strong></th> <th scope="col" style="text-align:justify"><strong>Quantity</strong></th> <th scope="col" style="text-align:justify"><strong>Total Price ($)</strong></th> </tr> </thead> <tbody> <tr> <td style="text-align:justify">ESP8266_ESP12E</td> <td style="text-align:justify">2.40</td> <td style="text-align:justify">1</td> <td style="text-align:justify">2.40</td> </tr> <tr> <td style="text-align:justify">Power-Supply&nbsp;110V/220V to 3.3V 700mA AC-DC</td> <td style="text-align:justify">1.47</td> <td style="text-align:justify">1</td> <td style="text-align:justify">1.47</td> </tr> <tr> <td style="text-align:justify">OptoCoupler MOC3081</td> <td style="text-align:justify">0.88</td> <td style="text-align:justify">2</td> <td style="text-align:justify">1.76</td> </tr> <tr> <td style="text-align:justify">Triac L4008D6 or T405-600B</td> <td style="text-align:justify">0.90</td> <td style="text-align:justify">2</td> <td style="text-align:justify">1.80</td> </tr> <tr> <td style="text-align:justify">Resistance RC1206JR-07330RL</td> <td style="text-align:justify">0.01</td> <td style="text-align:justify">4</td> <td style="text-align:justify">0.04</td> </tr> <tr> <td style="text-align:justify">Resistance&nbsp;OM16G5E-R58</td> <td style="text-align:justify">0.06</td> <td style="text-align:justify">2</td> <td style="text-align:justify">0.12</td> </tr> <tr> <td style="text-align:justify">Temperature Sensor:&nbsp;</td> <td style="text-align:justify">0.51</td> <td style="text-align:justify">1</td> <td style="text-align:justify">0.51</td> </tr> <tr> <td style="text-align:justify">Connector ATB612-508-2P</td> <td style="text-align:justify">0.08</td> <td style="text-align:justify">1</td> <td style="text-align:justify">0.08</td> </tr> <tr> <td style="text-align:justify">Connector ATB612-508-3P</td> <td style="text-align:justify">0.08</td> <td style="text-align:justify">1</td> <td style="text-align:justify">0.08</td> </tr> <tr> <td style="text-align:justify">pin header&nbsp;1x8</td> <td style="text-align:justify">0.02</td> <td style="text-align:justify">1</td> <td style="text-align:justify">0.02</td> </tr> <tr> <td style="text-align:justify">double sided PCB (from firstPCB)</td> <td style="text-align:justify">1.84</td> <td style="text-align:justify">1</td> <td style="text-align:justify">1.84</td> </tr> <tr> <td style="text-align:justify"><strong>TOTAL</strong></td> <td style="text-align:justify">&nbsp;</td> <td style="text-align:justify">&nbsp;</td> <td style="text-align:justify"><strong>10.12</strong></td> </tr> </tbody> </table> <p style="text-align:justify">So basically, we are at the level of 10$ per device.&nbsp; The cheapest commercial device I found on the web was around 50$, and they were not as complete as these. Note that there is still some soldering to be done to put all these together... so we are not completely comparing apple to apple.&nbsp; Note however that manual shutter switches already cost more than 10$.<br /> &nbsp;</p> <h3><strong>3) From prototyping to production:</strong></h3> <p style="text-align:justify">Before ordering all the components and making a PCB order in china (which usualy take a month), let&#39;s make a prototyping board on a home-made PCB.&nbsp; &nbsp;The manual shutter switch (first figure) was removed and replaced by the prototype that was inserted into the wall (seecond figure).&nbsp; Note that I am not able to make double sided PCB at home which explains why they are some wires here and there on the prototype.&nbsp; The prototype stayied in place for an entire week which also allowed me to make progress on the software (see next chapter).&nbsp; As I haven&#39;t observed any issue regarding WiFi connectivity during the testing period I move forward with the production of 10 devices (last figure).&nbsp;&nbsp;</p> <p style="text-align:center">&nbsp;</p> <div class="row"> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2018/04/11/IMG_20180410_131109.jpg" style="width:45%" /></p> </div> <div class="row"> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2018/04/11/14495438_10154537229118769_6013214470650004709_n.jpg" style="width:45%" /></p> </div> <div class="row"> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2018/04/11/14666284_10154622932583769_5130160508123201467_n.jpg" style="width:45%" /></p> </div> <p style="text-align:center">&nbsp;</p> <h3><strong>4) Software Development:</strong></h3> <p style="text-align:justify">One of the major advantage of the ESP8266 chip is that it is compatible with the Arduino software stack that is widely adopted by the hacker community world wide.&nbsp; There are therefore already many librariries available to do practically anything. See the <a href="https://github.com/esp8266/Arduino">ESP8266 Arduino github </a>for a complete documentation.&nbsp; So here the software part is mostly reduced to just combinning all the libraries together.&nbsp; I won&#39;t go in to the details of my code, but instead, I will list the functionnality that I have implemented in the software.&nbsp;&nbsp;</p> <ul> <li style="text-align:justify">The device is visible as a WiFi (password protected) access point to which we can connect to define login/password of the home wifi network&nbsp; (so the chip can connect itself as a station to the home wifi).&nbsp; We can also use this mode to rename the device and set the MQTT Broker address, port and credentials.</li> <li style="text-align:justify">The device connects as a station to the home wifi and stay connected.&nbsp; The device also runs a SSDP and mDNS servers which allows the network to discover the device and get it&#39;s hostname and other details =&gt; that typically allows windows to properly list the device in the network center and provide easy access to the device web interface from windows.</li> <li style="text-align:justify">The device run a webserver with a list of endpoints to <ul> <li style="text-align:justify">Retrieve onboard temperature</li> <li style="text-align:justify">Roll-up / Roll-Down / Stop / Tilt the shutter controlled by the device</li> <li style="text-align:justify">Retrieve device uptime</li> <li style="text-align:justify">Retrieve hardware and software details (software version, MAC address, IP address, device name, connected clients to the device access point, etc.)</li> <li style="text-align:justify">Interact with an external I2C device (that can be connected to the device thanks to connector JP1)</li> <li style="text-align:justify">Set MQTT Broker address, port and credentials</li> <li style="text-align:justify">Update a new firmware via Over The Air programming</li> <li style="text-align:justify">Reboot the device</li> <li style="text-align:justify">...</li> </ul> </li> <li style="text-align:justify">The device run a MQTT client which basically give the same functionnality than the webserver.&nbsp; The client listen on the&nbsp;topic &lt;DEVICE_NAME&gt;/in&nbsp;and publishes to the topic&nbsp;&lt;DEVICE_NAME&gt;/out/log, where basically every action are pushed so we can easilly monitor a fleet of devices from our favorite message broker (i.e. mosquitto).&nbsp; We can also imagine adding other publishing topics for instance to push temperatue measurements every 5 minutes.</li> <li style="text-align:justify">In case of WiFi disconnection, the device wait a bit and then try to reconnect.</li> </ul> <h3>&nbsp;</h3> <h3><strong>5) Central Server:</strong></h3> <p style="text-align:justify">Finally, we need one more piece to orchestrate all the devices at once from a nice user interface.&nbsp; This last piece is a small server that can host the MQTT broker and server as a bridge between the user and the devices.&nbsp; For this, we have used an old raspberry pi on which we have installed the mosquitto MQTT broker and on which we have also deployed a small django website from which we can roll the shutters up and down individuall or all at once.&nbsp; In addition, the django server also run a cron scheduler which automatically roll the shutters up in the morning and&nbsp;down in the evening.&nbsp; More complex logic can also be implemented in there to take actions according to special conditions liek temperature, vacation, etc.&nbsp; This sever is also used as an insulation layer between the wide internet and the secured local network.&nbsp; See the figure bellow to see how this small&nbsp;user interface looks like:</p> <p style="text-align:justify">&nbsp;</p> <div class="row"> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2018/04/11/Untitled.png" style="width:80%" /></p> </div> <p>&nbsp;</p> <h3 style="text-align:justify"><strong>Conclusion:</strong></h3> <p>We have conducted an entire IoT project from A to Z satisfying a specific list of constrains.&nbsp; &nbsp;We&#39;ve designed the electronic shema for the device, built a prototype, made a small production of final devices, develop their software, setup a server to communicate with these devices using either REST API or MQTT and finally, we have built a simple but handy user interface to orchestrate all these devices.</p> <p>&nbsp;</p> <p><strong>Do you have needs for something similar ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> loic.quertenmont@gmail.com (Loic Quertenmont)Wed, 11 Apr 2018 11:17:28 +0000http://deeperanalytics.be/blog/2018/04/11/building-wifi-based-iot-device-home-automation/Business CaseInternet Of ThingsReverse Image Search in Aerial and Satellite pictures http://deeperanalytics.be/blog/2017/05/19/reverse-image-search-satellite-images/<p>In this blog post, we will see how we can use reverse image search based on (unsupervised) convolutional neural networks to make the analysis of satellite/aerial pictures both more efficient and simpler. After reading this post, you will be able to find similar objects in a large aerial/satellite images and from there develop your own GIS statistical applications (i.e. to count all white cars in your neighborhood, identify specific road markings or kind of trees, etc. ).</p> <h3 style="text-align:center">&nbsp;</h3> <h3><strong>Introduction</strong></h3> <p style="text-align:justify">If you haven&#39;t done it yet, we suggest you start this blog by reading our previous blog <a href="/blog/C/">post introducing the concepts behind reverse image search algorithm</a>.&nbsp; We are reusing here all the concepts and technologies that we have previously introduced there.&nbsp; In particular, we are going to reuse the same topless VGG16 algorithm.&nbsp; A large aerial picture of a west coast neighborhood is used as a benchmark for today&#39;s blog.&nbsp; The benchmark picture was taken from the <a href="https://www.kaggle.com/c/draper-satellite-image-chronology">Draper satellite image chronology kaggle competition</a>.</p> <p style="text-align:justify">The main differences with respect to the introduction blog post are listed bellow.</p> <h3><strong>Large pictures</strong></h3> <p style="text-align:justify">Satellite or aerial pictures are generally quite large compared to the size of the objects of interest in the pictures.&nbsp; For instance, the dimension of our benchmark picture is 3100x2329 pixels while the typical size of a car on the picture is about 40x20 pixels.&nbsp; In comparison, in the introductory course, we had 104 pictures of at most 166x116 pixels.</p> <p style="text-align:justify">The best way to handle this difference is to divide the picture into same-size tiles.&nbsp; We compute the picture DNA of each tile using the topless VGG16 algorithm introduced earlier and later on, we will build up a similarity matrix by computing the cosine similarity between all pairs of tiles DNA.&nbsp; The size of the tiles should be slightly larger than the size of the objects we are interested in.&nbsp; In this blog, we are interested in objects with a scale of the order of a meter and we&#39;ve therefore decided to use tiles of 56x56 pixels.&nbsp; The figure bellow shows the benchmark picture with a grid of 56x56 cells.</p> <p style="text-align:justify">ConvNets are performing better at identifying object features when those are well centered on the image.&nbsp; Therefore, we need to guarantee that objects of interest are never shared between two tiles.&nbsp; We don&#39;t want the tile separation grid to cut the objects in two.&nbsp; The best way to guarantee that is to duplicate all tiles with an offset of half the size of a tile.&nbsp; Of course, we need to apply an offset either horizontally, vertically or both. The size of the offset is typically half of the tile size, but it can also be smaller.&nbsp; In today&#39;s benchmark, we are using an offset of 14 pixels (a quarter of the tile size).&nbsp; With such an offsetting, the number of tiles is multiplied by 16.&nbsp; This guarantees that the object of interest is always centered on at least one tile and thus to have the ConvNet extracting properly the object features.</p> <p style="text-align:justify">A note about computing time: The total number of tiles is inversely proportional to the square of the tile size.&nbsp; The computing time to compute the tile DNA scales linearly with the number of tiles, but the similarity matrix computation scales quadratically with the number of tiles.&nbsp; So you should perhaps think twice before using tiny tiles.</p> <p>&nbsp;</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/16/aerialPicture.png" style="height:100%; width:100%" /></p> <p>&nbsp;</p> <h3 style="text-align:justify"><strong>Pictures from the sky</strong></h3> <p style="text-align:justify">By definition, satellite or aerial pictures are taken from the sky....&nbsp; this might appear as a negligible detail for what we are trying to achieve, but it is not.&nbsp; As previously explained, the VGG16 algorithm was trained using the ImageNet dataset that is made of &quot;Picasa&quot;-like pictures.&nbsp; These pictures have a vertical orientation.&nbsp; This means that cars always have their wheels at the bottom of the pictures.&nbsp; Similarly, characters and animals have their head above their legs in most of the pictures.&nbsp; The algorithm has learned that the pictures aren&#39;t invariant against rotation or top/down flipping.&nbsp; In other words, the meaning of a car picture with the wheels at the top is very different than the same picture with the wheels pointing down the road.</p> <p style="text-align:justify">On the contrary, pictures from the sky have an invariant meaning against either flip symmetry or rotation.&nbsp; In aerial pictures, the orientation of the object is totally random and has no particular meaning.&nbsp; A picture of a car driving toward the west or the east is still the picture of a driving car.&nbsp;</p> <p style="text-align:justify">Since the VGG16 algorithm has learned to account for the orientation of the image, we will need some extra steps to make our similar picture finder insensitive to the orientation.&nbsp; We have two options:</p> <p style="text-align:justify">Option1:&nbsp; we retrain the VGG16 algorithm to learn that picture A and picture A flipped or picture A rotated must have the same DNA vector.&nbsp; This is a fully unsupervised learning, as we don&#39;t need a set of labeled pictures to perform this training.&nbsp; we can use randomly chosen tiles from the main picture to perform this training.&nbsp; However, we would still need a relatively large amount of time to perform this retraining.&nbsp; In addition, there is some risk that the performance of the VGG16 algorithm at identifying picture features get reduced by this loss of picture orientation.&nbsp; This option might be worth doing if you consider a larger project with a gigantic amount of tiles to process.</p> <p style="text-align:justify">Option2:&nbsp; instead of computing just one DNA vector per picture, we can compute one DNA vector per picture and per symmetry transformation.&nbsp; Then, we&#39;ll try to identify similar images, we can compare the reference picture DNA vector to the vectors of all other pictures and their symmetries.&nbsp; &nbsp; This approach is a bit heavier in terms of CPU when computing the similarity matrix, but the implementation is straight forward.&nbsp; Note that the VGG16 is expected to be already insensitive to left/right symmetry, so the only symmetry that we should consider are actually rotations.&nbsp; The top/down symmetry is not needed either as it could be decomposed as an 180-degree rotation followed by a left/right symmetry.<br /> <br /> For today&#39;s benchmark, we opted for the option2 and the symmetry that we considered are simply the four 90-degree rotations.&nbsp; This virtually increases the number of tiles by yet a factor 4.&nbsp; In total, we have considered 143664 tiles+symmetries in this benchmark demo.&nbsp; This leads to the computation of about 10 billions cosine similarities.</p> <p>&nbsp;</p> <h3><strong>Full Demo</strong></h3> <p style="text-align:justify">For this benchmark, we have created a full application demo with:</p> <ul> <li> <p style="text-align:justify"><a href="https://d3js.org/">a D3js frontend:</a> which allows having a nice interface on top of the algorithm.&nbsp; The D3js interface that we built allows zooming on a specific area of the picture (using mouse wheel), to move within the picture by dragging it and to select an area of interest by clicking on the picture.&nbsp; Once an area of interest is selected, the 10 most similar area are highlighted.&nbsp; Moving the mouse over those area shows the value of the cosine similarity.</p> </li> <li> <p style="text-align:justify">a <a href="https://www.djangoproject.com/">Django</a> backend:&nbsp; where the hard code processing is being executed.&nbsp; The Django backend allows us to execute our python code on the fly and ease the access to the database storing precomputed cosine similarity for all the tile+symmetry pairs.</p> </li> <li> <p style="text-align:justify">a database with an index: which allows returning the most similar area associated to a specific tile in no time.&nbsp;</p> </li> <li> <p style="text-align:justify">a result filtering module:&nbsp; Because we have several tiles covering the same area (or a part of the area) due to the offsetting strategy we choose, we have good chances that the algorithm find that the most similar areas are the reference area shifted by a small offset (a quarter of the tile size in our benchmark example).&nbsp; Although this is indeed a valid result, it is for sure not the interesting results we are naively expecting.&nbsp; So we have added a cross-cleaning module that rejects tiles from the result list that are either partially covering the reference area or a tile&nbsp;with a better similarity score that is already in the result list</p> </li> </ul> <p style="text-align:justify">The full demo is visible at the bottom of this blog post but is also available on this&nbsp;<a href="http://deeperanalytics.be/ImageSearch/">page.</a></p> <p>&nbsp;</p> <h2><strong>Performance on specific examples</strong></h2> <p style="text-align:justify">In this section, we are discussing the performance of the algorithm at finding similar objects for some specific examples.&nbsp; You can repeat these tests by yourself in the demo app. &nbsp;In the series of picture bellow, the top-left image shows the reference area (the one for which we are trying to find similar matches) in blue.&nbsp; The rest of the image is just there to show the reference tile in its context.&nbsp; The 9 other pictures show the closest matches in their context. &nbsp;For tiles that are close to the main picture borders, the missing context (outside the picture) is shown as a black area, &nbsp; For the matches, the similarity score is also given for reference.&nbsp; In both the reference and matches pictures, the tile coordinates are also given, so you can try to locate the tile in the main picture (or in the demo app).&nbsp;</p> <h3 style="text-align:justify"><strong><u>Road marking:</u></strong></h3> <p style="text-align:justify">The reference object is a usual road marking symbol.&nbsp; In the 9 similar tiles proposed by the algorithm, we see that 6 of them have the same road marking. The&nbsp; 3 others are clearly false positive, but if we look closer at the reference picture, we can notice that it captures a car side on the left and right of the images as well as large area of road.&nbsp; The 3 false positive pictures don&#39;t have the road marking, but still, have these 3 other features, so it could also be considered to be a similar image.&nbsp; We could avoid this false match, by adapting the reference area to the exact size of the road marking if we need to develop such a marking finder application.</p> <p style="text-align:justify">&nbsp;</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/15/SatSearch_Sign.png" style="height:100%; width:100%" /></p> <h3><strong><u>Dark cars:</u></strong></h3> <p style="text-align:justify">The reference object is a rather dark vehicle parked along the sidewalk.&nbsp; In the 9 similar tiles proposed by the algorithm, we see that 4 of them have the same type of vehicle in the same context.&nbsp;&nbsp; The others are clearly false positive, generally showing roof parts.&nbsp; Here the algorithm clearly focuses on the color pattern of the reference image where the dark gray area of the road is close to a lighter area from the sidewalk.&nbsp; Interestingly, a similar type of patterns caused by light/shadow effects is also present in some house roof pictures.</p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/15/SatSearch_car_dark.png" style="height:100%; width:100%" /></p> <h3><strong><u>White cars:</u></strong></h3> <p style="text-align:justify">This time, the reference object is a white vehicle parked along the sidewalk.&nbsp; In the 9 similar tiles proposed by the algorithm, we see that 6 of them are really perfect matches.&nbsp;&nbsp; Another one picked up a colored&nbsp;car instead of a white one.&nbsp; And the 2 others are just showing a reference picture with 50% of roads and 50% of sidewalks.&nbsp; In these cases, the algorithm focused on features of the background rather than on the vehicle.&nbsp; We can also notice that in these images, there is a rectangular shape on the sidewalk which may be interpreted as a vehicle by the algorithm.<br /> &nbsp;</p> <p>&nbsp;</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/15/SatSearch_car_onroad.png" style="height:100%; width:100%" /></p> <h3><strong><u>White cars in front of a house:</u></strong></h3> <p style="text-align:justify">We can play the same game with a white car that is parked in front of a house this time.&nbsp; This time we have a perfect score.&nbsp; All pictures are indeed showing similar pictures.</p> <p style="text-align:justify">&nbsp;</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/15/SatSearch_car_athouse.png" style="height:100%; width:100%" /></p> <p>&nbsp;</p> <h3><strong><u>Road, sidewalk, and grass:</u></strong></h3> <p style="text-align:justify">Let&#39;s try a picture made of three &quot;background&quot; parts:&nbsp; road, sidewalk and some grass on the corner.&nbsp; In the absence of a &quot;main&quot; object, the algorithm may focus on unexpected picture features, so it&#39;s an interesting example.&nbsp; We would say that the algorithm is providing meaningful pictures in 8 out of 9 of the cases.&nbsp; On these pictures, there is indeed always some part of roads, sidewalks, and grass.&nbsp; The proportion of each component may differ significantly, but they are always present.&nbsp; Sometimes we have an extra object in addition of these components (eg. a car or a bush), but that&#39;s OK. &nbsp;We also count a false positive again caused by light/shadow effect on a roof.</p> <p style="text-align:justify">&nbsp;</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/15/SatSearch_Sidewalk.png" style="height:100%; width:100%" /></p> <p>&nbsp;</p> <h3 style="text-align:justify"><strong><u>Road corner:</u></strong></h3> <p style="text-align:justify">Another complex example is this reference picture showing a road corner (with a curved sidewalk and some grass).&nbsp; The four firsts pictures are quite positive matches?&nbsp; The first one, in particular, is almost identical to the reference picture.&nbsp; We can also note that the algorithm also catches road turning in a quite different way. &nbsp;The other 5 pictures are totally wrong which is consistent with their relatively low similarity score.&nbsp; A quick look at the global picture easily gives us the explanation:&nbsp; there is actually only very few part of the images showing some road corners, so the algorithm as a hard time finding a similar area.&nbsp; So it gives what he can find that is showing similar features,&nbsp; We can, for instance, notice the circular swimming pool that has almost the same bend radius than the road corner.<br /> &nbsp;</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/15/SatSearch_roundedroad.png" style="height:100%; width:100%" /></p> <p>&nbsp;</p> <h3><strong><u>Solar panels:</u></strong></h3> <p style="text-align:justify">Finding solar panels on house roof from aerial pictures has a lot of application in marketing, statistics, and energy forecast.&nbsp; So it is interesting to see how well the algorithm is performing at this simple task.&nbsp; We got 3/9 matches which are indeed showing solar panels.&nbsp; Three pictures are showing roof with some objects on it.&nbsp; And the last three are complete false positive.&nbsp; But looking again at the global picture, we can notice that the number of roofs with solar panels are actually quite limited in this neighborhood, so again, the algorithm has a hard time finding matches and does what he can.&nbsp; Moreover, the size of tile is not necessarily appropriate at finding objects of that size.</p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/15/SatSearch_Solarpanel.png" style="height:100%; width:100%" /></p> <h3><strong><u>Palm trees:</u></strong></h3> <p style="text-align:justify">Finding specific species of trees also have many sorts of daily life applications.&nbsp; Is this algorithm capable of making the difference between a palm tree and an oak?&nbsp; Let&#39;s try.<br /> We picked up a reference picture clearly showing a palm tree next to a house.&nbsp; The matches show 6 pictures showing palm trees while the three other matches are showing other sorts of trees.&nbsp; What is interesting is that in some cases, the algorithm has a better match on the shadow of the tree rather than on the three itself.&nbsp; This is quite unexpected, but looking closer at those matching picture, we would say that this is also true for the human eye.</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/15/SatSearch_palmtree.png" style="height:100%; width:100%" /></p> <p>&nbsp;</p> <h2><strong>Full Demo:</strong></h2> <p style="text-align:justify">You can find bellow the full demo that we have built up using the topless VGG16 algorithm wrapped in a Django backend and a D3js frontend.&nbsp;</p> <p style="text-align:justify">Click on an area of interest in the satellite image below. The Deeper Solution algorithm for reverse image search will find the 10 tiles in the region that look the most similar to the place you selected. &nbsp;Move the mouse over a picture match (blue square) to see its similarity score compared to the reference area (red square).&nbsp; You can zoom on the picture using the mouse wheel and pan/move the picture by holding the mouse button down while moving the mouse.&nbsp; &nbsp; <a href="http://deeperanalytics.be/ImageSearch/">Open the demo in a separated window.</a></p> <p style="text-align:justify">Have fun!</p> <p><iframe align="top" frameborder="0" height="700px" scrolling="no" src="https://deeperanalytics.be/ImageSearch/Raw/" width="100%"></iframe></p> <p>&nbsp;</p> <h2><strong>Conclusion</strong></h2> <p style="text-align:justify">In conclusion, we have seen that convolutional neural networks are quite helpful at finding similar areas in either aerial or satellite pictures.&nbsp; We have demonstrated that encoded small pictures area into small feature-sensitive DNA vector make the picture finder both efficient and fast.&nbsp; We were able to find very specific objects like palm trees, solar panels or&nbsp;specific types of cars, without even training the algorithm at recognizing those objects.&nbsp; A small benchmark application was developed to demonstrate the easiness of deploying such a technique for business solutions.&nbsp; Moreover, the approach and the algorithm can easily be scaled to extremely large pictures (or picture collections) covering large cities or even countries using distributed computing (through Apache Spark for instance).</p> <p style="text-align:justify">&nbsp;</p> <p><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;&nbsp;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> <p>&nbsp;</p> <p>&nbsp;</p> loic.quertenmont@gmail.com (Loic Quertenmont)Fri, 19 May 2017 05:30:49 +0000http://deeperanalytics.be/blog/2017/05/19/reverse-image-search-satellite-images/Business CaseDeep learningGeoSpatialReverse image search based on (unsupervised) convolutional neural network http://deeperanalytics.be/blog/2017/05/12/unsupervised-usage-convolutional-neural-networks-cnn-better-satellite-image-analysis/<p>The requirement for a (very) large training set is generally the main criticism that is formulated against deeplearning algorithms. In this blog, we show, how deep convolutional neural networks (CNN) can be used in an unsupervised manner to perform efficient reverse image search.</p> <p style="text-align:justify">The requirement for a (very) large training set is generally the main critic&nbsp;that is formulated against deeplearning algorithms.&nbsp; In this blog, we show how deep convolutional neural networks (CNN) can be used in an unsupervised manner to identify similar images in a set.&nbsp; At first, we remind the general concepts behind convolutional neural networks.&nbsp; In a second step, we discuss the VGG16 algorithm and how it was trained.&nbsp; We then explain the concepts of transfer learning and retraining.&nbsp; After that, we have all the ingredients to introduce the reverse image search algorithm.&nbsp; Finally, we will show a demo of the reverse image search performance with a deck of cards.<br /> &nbsp;</p> <h2><strong>Convolutional neural network</strong></h2> <p style="text-align:justify">Convolutional neural network (CNN or ConvNet) is a type of artificial neural network that was initially developed by <a href="https://en.wikipedia.org/wiki/Yann_LeCun">Yann LeCun</a> (and friends) for image recognition.&nbsp; As for the regular neural network, they are made up of neurons that have learnable weights and biases. Each neuron receives some inputs, performs a linear operation (input * weights + bias) and optionally follows it with a non-linear activation function (generally a Sigmoid or a Relu). The major difference with respect to a regular neural network is that ConvNets are translation invariant.&nbsp; This an important property for object recognition in images because we want the neural network to identify the object regardless on its position on the image.&nbsp; Intuitively, one way to achieve this requirement would be to divide the picture into many smaller-size pads and apply a unique (reduced) neural network on each of these pads.&nbsp; Therefore, if the object that we are trying to identify is present on the bottom-left corner of the image instead of the top-right corner, we would still be capable of finding it as soon as we have a picture pad covering that region.&nbsp; We can actually go further and split the pads into smaller pads made up of the constituent of the objects we are trying to identify.&nbsp; Etc. Etc.&nbsp; This is the general idea behind convolutional neural networks.</p> <p style="text-align:justify">ConvNets are generally made of two alternating types of layers:</p> <ul> <li style="text-align:justify"> <p>Convolutional Layers<br /> This is the layer that does most of the computational work.&nbsp; It is made of a certain number of learnable kernels/filters that are have reduced width/height extension compared to the size of the image but are sensitive to the full image depth (used to encode the color of the image pixels).&nbsp; During the forward pass of the network, the filters are locally convoluted with all possible image pads of the filter dimension.&nbsp; The local (convolution) output of the layer is then passed through an activation function (generally a Relu).&nbsp; When the learnable pattern of the filter matches the local pattern of the image, the output of this process is generally close to 1 otherwise it is close to 0.&nbsp; The convolutional layer output is, therefore, a local indicator of pattern identification on the picture.&nbsp;<br /> The filter patterns are learned using regular neural network backpropagation and gradient descent technique.</p> </li> </ul> <ul> <li style="text-align:justify"> <p>Pooling Layers<br /> The main goal of the pooling layer is to reduce the spatial size of the image representation (after a convolutional layer) in order to reduce the number of learnable parameters in subsequent layers.&nbsp; This layer is basically a non-linear downsampling of the image representation.&nbsp; Very often, the spatial dimension is reduced by a factor n=2 or 3 by keeping only&nbsp;the value of the largest pixel in a group of n x n pixels.&nbsp;</p> </li> </ul> <p style="text-align:justify">A ConvNet is generally made up of several pairs of convolutional + pooling layers and ended with one or two fully connected layers as we can find them in a multi-layer perceptron.&nbsp; The first convolutional layers generally learn to identify simple (very local) patterns like vertical lines, horizontal lines, diagonals, etc.&nbsp; While the following layers are made up of patterns of these simple patterns are good at identifying more complex patterns.&nbsp; For instance, could learn that a car is made up of &quot;wheel&quot; and &quot;windshield&quot; patterns.&nbsp;&nbsp; The purpose of the final layers is to make a prediction based on the presence of pattern at specific locations in the image representation.&nbsp; The figure bellow, taken from this excellent <a href="https://adeshpande3.github.io/A-Beginner%27s-Guide-To-Understanding-Convolutional-Neural-Networks/">blog</a>, shows the typical architecture of a ConvNet.</p> <p style="text-align:justify">We stop here the ConvNet introduction as we don&#39;t need a deep theory understanding for the following of this blog.&nbsp; If you&#39;d like to know more about CNN, the web is full of pedagogical introductive courses to ConvNets (see for instance <a href="http://cs231n.github.io/convolutional-networks/">this</a>, <a href="https://adeshpande3.github.io/A-Beginner%27s-Guide-To-Understanding-Convolutional-Neural-Networks/">this</a> or <a href="https://en.wikipedia.org/wiki/Convolutional_neural_network">this</a>).&nbsp;</p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/10/ConvNetArchitecture.png" style="height:100%; width:100%" /></p> <h2><strong>ImageNet and the VGG16 network</strong></h2> <p style="text-align:justify">The total number of free parameters in a convNet is significantly smaller than for a multi-layer perceptron achieving the same job.&nbsp; Nonetheless, the number of parameters to learn in a modern convNet is generally still pretty large (order of billions) and require a large training set to avoid overfitting.</p> <p style="text-align:justify">The purpose of the <a href="http://image-net.org/">ImageNet database</a> is to provide such training datasets in order to accelerate the research in image recognition.&nbsp; Thanks to imageNet, we have access to 15M pictures that are labeled by one or more category.&nbsp; They are currently about 20K different category label (or synsets).&nbsp; In addition, the ImageNet group organizes yearly image recognition challenges where the word class researchers can compare the performances of their algorithms.&nbsp; For this blog, we are in particular interested in the <a href="http://image-net.org/challenges/LSVRC/2014/index#task">Task 2a of the 2014 contest</a>, The goal was to classify 50K pictures among a 1000 different label categories.&nbsp; Identified object are also asked to be localized on the picture.&nbsp; A training set of 1.2M images was provided.</p> <p style="text-align:justify">One of the main winners of this competition is the <a href="https://arxiv.org/abs/1409.1556">VGG16</a> algorithm that was developed by the famous Visual Geometry Group (VGG) of the University of Oxford.&nbsp; The algorithm is documented in great details in this <a href="http://arxiv.org/abs/1409.1556">scientific article</a>.&nbsp; They are also many pedagogical introductions to the algorithm all over the web, so I am just going to give a brief summary here.&nbsp; The VGG16 algorithm is made up of 5 convolutional blocks followed by 3 fully-connected dense layers.&nbsp; The inputs are images of size 224 pixels (width) x 224 pixels (height) x 3 color components (depth) and the output is a vector of 1000 components giving the probability that the input image belong to each of the possible label categories.&nbsp; The network is made of 16 &quot;active&quot; layers that are listed hereafter.&nbsp; The number in parenthesis provides the tensor dimensions of each layer outputs:</p> <ul> <li> <p>Input Layer (224x224x3)</p> </li> <li> <p>CNN Block 1</p> <ul> <li> <p>Convolutional Layer1 (224x224x64)</p> </li> <li> <p>Convolutional Layer2 (224x224x64)</p> </li> <li> <p>Pooling Layer (112x112x64)</p> </li> </ul> </li> <li> <p>CNN Block 2</p> <ul> <li> <p>Convolutional Layer1 (112x112x128)</p> </li> <li> <p>Convolutional Layer2 (112x112x128)</p> </li> <li> <p>Pooling Layer (56x56x128)</p> </li> </ul> </li> <li> <p>CNN Block 3</p> <ul> <li> <p>Convolutional Layer1 (56x56x256)</p> </li> <li> <p>Convolutional Layer2 (56x56x256)</p> </li> <li> <p>Convolutional Layer3 (56x56x256)</p> </li> <li> <p>Pooling Layer (28x28x256)</p> </li> </ul> </li> <li> <p>CNN Block 4</p> <ul> <li> <p>Convolutional Layer1 (28x28x512)</p> </li> <li> <p>Convolutional Layer2 (28x28x512)</p> </li> <li> <p>Convolutional Layer3 (28x28x512)</p> </li> <li> <p>Pooling Layer (14x14x512)</p> </li> </ul> </li> <li> <p>CNN Block 5</p> <ul> <li> <p>Convolutional Layer1 (14x14x512)</p> </li> <li> <p>Convolutional Layer2 (14x14x512)</p> </li> <li> <p>Convolutional Layer3 (14x14x512)</p> </li> <li> <p>Pooling Layer (7x7x512)</p> </li> </ul> </li> <li> <p>Flatten Layer (25088 = 7x7x512)</p> </li> <li> <p>Dense Layer (4096)</p> </li> <li> <p>Dense Layer (4096)</p> </li> <li> <p>Dense (Softmax) Layer (1000)</p> </li> </ul> <p style="text-align:justify">The accuracy of the algorithm at identifying 1000 different objects on images is about 93%.&nbsp; This is a very high accuracy when we know that the accuracy of humans for the same task is about 95% as explain in this <a href="https://arxiv.org/abs/1409.0575">paper</a>.&nbsp; Today, they are more sophisticated algorithms that can even outperform human performances for such tasks.</p> <p style="text-align:justify">Now that we know the VGG16 network configuration and that we have a large publicly available training set, we are ready to train the algorithm...&nbsp; We only need to use the 1.5million training set to learn 139 million learnable parameters through backpropagation...&nbsp; Hum... that&#39;s going to really take a while on a personal computer.&nbsp; Computing resources are actually the bottleneck here....</p> <p style="text-align:justify">Fortunately, pre-trained weights for the VGG16 algorithm (and many others) are publicly available on the <a href="https://github.com/BVLC/caffe/wiki/Model-Zoo">Caffe Model Zoo web page</a>.&nbsp; The python deeplearning library <a href="https://keras.io/">Keras</a> even have automatic scripts to download the weights when needed.&nbsp; So we have in our hands one of the most accurate algorithms for image recognition that is already pre-trained and ready to use.<br /> &nbsp;</p> <h2><strong>Transfer Learning&nbsp; </strong></h2> <p style="text-align:justify">What is interesting with convolutional neural networks, is that each of the convolutional layers is learning specific image features (lines, colors, patterns, groups of pixels, etc.).&nbsp; It&#39;s only at the very end of the network that all these image features are connected to each other in view of tagging the entire image.&nbsp; So, although the end of the network is highly specialized at recognizing the thousand possible labels of the 2014 ImageNet contest, the lower parts of the network are more generic and could be recycled for other tasks without retraining.&nbsp; This is often referred to as &quot;transfer learning&quot;.</p> <p style="text-align:justify">Thanks to transfer learning, we could replace the last layer(s) of the network by something that is more dedicated to our use case (different than the 1000 ImageNet labels).&nbsp; For instance, if we are interested in finding whether our image contains either a Cat or a Dog, we would have only two different labels and our last layer could be made of only two neurons (compared to the 1000 of the VGG16).&nbsp; If we assume that the rest of the network should remain unmodified, we would have to learn only 8194 free parameters (corresponding to 2 bias value and all weight connections between the 4096 neurons of the one to the last layer with the 2 neurons of the last layer).&nbsp; This is a very small number of parameters to learn for such a complex task.&nbsp; We would, therefore, need a relatively small training dataset to train our specialized VGG16 algorithm.</p> <p style="text-align:justify">Depending on the problem, the hypothesis the first VGG16 layers remains totally unchanged might not be always good.&nbsp; In these cases, we may want to entirely re-train the algorithm and therefore find the optimal values for all of the 139M parameters of the network.&nbsp; But even in that situation, the transfer learning is a useful thing as we could start with parameter values that are already close to their minimum.&nbsp; This would make the algorithm to converge much much faster.&nbsp; Training time could easily be reduced from weeks to hours timescale.<br /> &nbsp;</p> <h2 style="text-align:justify"><strong>Unsupervised usage of the VGG16 algorithm for Reverse Image Search</strong></h2> <p style="text-align:justify">So far, we have explained how we can train the VGG16 using super large training dataset, and how to &quot;recycle&quot; pre-trained model in order to accommodate a smaller training set to perform simpler tasks.&nbsp; But, we can go further and recycle the VGG16 pre-trained model to perform a very useful task that would not even need a small retraining.&nbsp; This would, therefore, move this to the category of &quot;<strong>unsupervised</strong>&quot; model that is immediately ready to use.&nbsp;</p> <p style="text-align:justify">If we drop the very last layer of the VGG16 (as shown in the figure bellow), the output of the network is a vector of 4096 components.&nbsp; Those components are the one that is used by VGG16 to make the difference between cats, dogs, cars and many other types of images.&nbsp; Thus, they contain lots of information about the content of the image in a relatively compact way.&nbsp; We like to see the output of the vector as the DNA of the picture that is analyzed by the last layer to name the content of the image.&nbsp; So, somehow after this layer, the object is already &quot;uniquely&quot; identified... it&#39;s just that we don&#39;t know how to call it.</p> <p style="text-align:justify">In the following, we would refer to this modified VGG16 algorithm as the &quot;topless&quot; VGG16.</p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/11/VGG16.jpeg" style="height:100%; width:100%" /></p> <p style="text-align:justify">They are many tasks that actually do not require to name the content of the image.&nbsp; Think for instance to a task where you have an image and you&#39;d like to identify all other images that look like this one.&nbsp; The topless VGG16 algorithm would be very good at this, as similar images would have similar DNA content.&nbsp; So the complex problem of finding similar images in a collection is transformed to the trivial problem of finding similar vectors.&nbsp; This is achieved very easily by linear algebra and more precisely by the dot product of the two vectors.&nbsp; If you are not familiar with this concept, you can search for <a href="https://en.wikipedia.org/wiki/Cosine_similarity">cosine similarity</a> on the web.</p> <p style="text-align:justify">Most of the <strong>Reverse Image Search </strong>services, like <a href="https://www.tineye.com/">TinEye</a> or <a href="https://en.wikipedia.org/wiki/Google_Images#Search_by_image">Google image search</a>, rely on such technique for comparing images.</p> <p style="text-align:justify">In the following of this blog, we will show how the technique perform in a simple example.<br /> &nbsp;</p> <h2><strong>Demonstration of the reverse image search on a deck of card</strong></h2> <p style="text-align:justify">We will show the performance of the topless VGG16 algorithms for identifying similar cards out of two shuffled decks of cards.&nbsp; In order to make the exercise a bit more tricky, we used two decks of cards with very different image styles and very different image resolutions and dimensions.&nbsp; The first one as 166x116 dimensions, while the second one has 91x67 dimensions.&nbsp; Both will be resized to 224x224 dimensions has this is the expected input size of the VGG16 algorithm.&nbsp; The figure bellow shows the 104 cards that we are going to use.</p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/DeckOfCards.png" style="height:100%; width:100%" /></p> <p style="text-align:justify">Every card is passed through the topless VGG16 in order to get its associated DNA vector.&nbsp; The vectors are then normalized to norm=1 and used to compute the similarity between the ith card and the jth card by computing the cosine similarity (dot product) of the two vectors.&nbsp; Finally, we can build the similarity matrix which would contain the similarity between all pair of cards.&nbsp; The matrix is analyzed to find out what are the 5 most similar cards to a given one.</p> <p style="text-align:justify">Let&#39;s look at the most similar cards (as predicted by the topless VGG16 also) for a few examples.&nbsp; In the pictures here after, the left most card is always the reference card for which we are trying to identify the top-5 most similar cards.&nbsp; The value of the cosine similarity between the two picture DNA vectors is also given at the top of most similar images.</p> <p style="text-align:justify"><strong>Example 1: The Ace of Diamonds</strong><br /> In both decks of cards, the most similar card to the ace of diamonds is identified as the ace of hearts of the same deck.&nbsp; Interestingly, for the second most similar card, we have different results depending on the deck.&nbsp; For deck1, the ace of diamonds of the other deck is tagged as the second to most similar.&nbsp; For deck2, on the contrary, it spots the 2 of diamonds as being more similar than the ace of the other deck.&nbsp; In both decks, the 2 and 3 of diamonds are marked as rather similar to the ace, which makes sense as in all cases, there are&nbsp;diamond symbols in the top left and bottom right of the card, and the overall card symmetry is similar.&nbsp;</p> <p>&nbsp;</p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deck_a1.png" style="height:100%; width:100%" /></p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deckB_a1.png" style="height:100%; width:100%" /></p> <p style="text-align:justify"><strong>Example 2: The Five of Diamonds</strong><br /> In both decks of cards, the most similar card to the five of diamonds is identified as the six of diamonds in the same deck.&nbsp; Interestingly, the following cards are similar in both decks and have all a rather close similarity value, which explain why the order is not necessarily identical.&nbsp; All these cards actually have a value that is close to 5:&nbsp; 3,4,6,7.&nbsp; This makes perfect sense.</p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deck_a5" style="height:100%; width:100%" /></p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deckB_a5.png.png" style="height:100%; width:100%" /></p> <p style="text-align:justify"><strong>Example 3: The Jack of Diamonds</strong><br /> Here, the image on the cards become much more complex and the behavior of the algorithm is, therefore, a bit less clear.&nbsp; What we noticed, is that the direction of the character face, the color of the character hair, the style of the weapons and the clothes colors are all playing an important role in the card similarity.&nbsp; These features matter much more than the card color.&nbsp; Wich also makes perfect sense as these features occupy quite a large part of the picture&nbsp; (much more than the card color symbols).&nbsp; We can also notice that the similarity between cards is also lower than in the previous case.</p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deck_a11.png" style="height:100%; width:100%" /></p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deckB_a11.png.png" style="height:100%; width:100%" /></p> <p style="text-align:justify"><strong>Example 4: The Queen of Diamonds</strong><br /> Similarly, to what was discussed in example 3, as the images are quite rich in terms of features, it is more difficult to understand the deep reasons behind the similarity scores.&nbsp; But clearly, the clothes styles seems to have quite some importance here.</p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deck_a12.png" style="height:100%; width:100%" /></p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deckB_a12.png.png" style="height:100%; width:100%" /></p> <p style="text-align:justify"><strong>Example 5: The King of Diamonds</strong><br /> Finally, we can have a look at the results for the king of diamonds.&nbsp; Interestingly, in the deck1, it identifies left looking &quot;male&quot; cards, then the right looking &quot;male&quot; and finally the queen of diamonds.&nbsp; In the second deck, the resolution of the picture is much worse, but we despite that we can see a similar kind of behavior.&nbsp;</p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deck_a13.png" style="height:100%; width:100%" /></p> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/05/12/CardSim_deckB_a13.png.png" style="height:100%; width:100%" /></p> <h2><strong>Conclusion</strong></h2> <p style="text-align:justify">In conclusion, we have seen that convolutional neural networks are quite large neural networks with hundreds of millions of learnable parameters that are generally hard to train by individuals with limited computing resources.&nbsp; However, we have also explained that transfer learning allows individuals to recycle pre-trained models for other purposes with minimal (re)training or even without retraining at all for the case of reverse image search.&nbsp; We have demonstrated that the convolutional neural networks are capable of extracting the features associated with an image into the form of a picture DNA vector.&nbsp; Comparing these DNA vectors allows identifying pictures that have the similar type of features and that are therefore similar to each other.</p> <p style="text-align:justify">&nbsp;</p> <p><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> loic.quertenmont@gmail.com (Loic Quertenmont)Fri, 12 May 2017 17:00:11 +0000http://deeperanalytics.be/blog/2017/05/12/unsupervised-usage-convolutional-neural-networks-cnn-better-satellite-image-analysis/Deep learningSparkling Water on the Spark-Notebook http://deeperanalytics.be/blog/2017/04/11/sparkling-water-spark-notebook/ <p style="text-align:justify"><em>Note:&nbsp; This blog post was written as a collaboration between <a href="http://www.kensu.io/" target="_blank">Kensu.io</a> and <a href="https://www.h2o.ai/" target="_blank">H2O.ai</a> and the blog content was initially posted on on <a href="http://blog.h2o.ai/2017/04/sparkling-water-on-the-spark-notebook/" target="_blank">blog.H2O.ia</a>.&nbsp; You can either read it here, or continue your reading on its original <a href="http://blog.h2o.ai/2017/04/sparkling-water-on-the-spark-notebook/">publication page</a>.</em></p> <p style="text-align:justify">In the space of Data Science development in enterprises, two outstanding scalable technologies are Spark and H2O. Spark is a generic distributed computing framework and H2O is a very performant scalable platform for AI.&nbsp; Their complementarity is best exploited with the use of Sparkling Water.&nbsp;&nbsp; Sparkling Water is the solution to get the best of Spark &ndash; its elegant APIs, RDDs, multi-tenant Context and H2O&rsquo;s speed, columnar-compression and fully-featured Machine Learning and Deep-Learning algorithms in an enterprise ready fashion.&nbsp; Examples of Sparkling Water pipelines are readily available in the <a href="https://github.com/h2oai/sparkling-water">H2O github repository</a>, we have revisited these examples using the Spark-Notebook.</p> <p style="text-align:justify">The <a href="http://spark-notebook.io/">Spark-Notebook</a> is an open source notebook (web-based environment for code edition, execution, and data visualization) focused on Scala and Spark.&nbsp; This is a notebook comparable to Jupyter. &nbsp; The Spark-Notebook is part of the <a href="http://www.kensu.io/">Adalog suite of Kensu.io</a> which addresses agility, maintainability and productivity for data science teams. Adalog offers to data scientists a short work cycle to deploy their work to the business reality and to managers a set of data governance giving a consistent view on the impact of data activities on the market.</p> <p style="text-align:justify">This new material allows diving into Sparkling Water in an interactive and dynamic way.</p> <p style="text-align:justify">Working with Sparking Water in the Spark-Notebook scaffolds an ideal platform for big data /data science agile development. Most notably, this gives the data scientist the power to:</p> <ul> <li style="text-align:justify">Write rich documentation of his work alongside the code, thus improving the capacity to index knowledge</li> <li style="text-align:justify">Experiment quickly through interactive execution of individual code cells and share the results of these experiments with his colleagues.</li> <li style="text-align:justify">Visualize the data he/she is feeding H2O through an extensive list of widgets and automatic makeup of computation results.</li> </ul> <p style="text-align:justify">Most of the H2O/Sparkling water examples have been ported to the Spark-Notebook and are available in a github repository.</p> <p style="text-align:justify">We are focussing here on the Chicago crime dataset example and looking at:</p> <ul> <li style="text-align:justify">How to take advantage of both H2O and Spark-Notebook technologies,</li> <li style="text-align:justify">How to install the Spark-Notebook,</li> <li style="text-align:justify">How to use it to deploy H2O jobs on a spark cluster,</li> <li style="text-align:justify">How to read, transform and join data with Spark,</li> <li style="text-align:justify">How to render data on a geospatial map,</li> <li style="text-align:justify">How to apply deep learning or Gradient Boosted Machine (GBM) models using Sparkling Water</li> </ul> <p style="text-align:justify">&nbsp;</p> <h2 style="text-align:justify">Installing the Spark-Notebook:</h2> <p style="text-align:justify">Installation is very straightforward on a local machine. Follow the steps described in the Spark-Notebook documentation and in a few minutes, you will have it working. Please note that Sparkling Water works only with Scala 2.11 and Spark 2.02 and above currently.<br /> For larger projects, you may also be interested to read the documentation on how to connect the notebook to an on-premise or cloud computing cluster.</p> <p style="text-align:justify">The Sparkling Water notebooks repo should be cloned in the &ldquo;notebooks&rdquo; directory of your Spark-Notebook installation.</p> <h2 style="text-align:justify">Integrating H2O with the Spark-Notebook:</h2> <p style="text-align:justify">In order to integrate Sparkling Water with the Spark-Notebook, we need to tell the notebook to load the Sparkling Water package and specify custom spark configuration, if required. Spark then automatically distributes the H2O libraries on each of your Spark executors. Declaring Sparkling Water dependencies induces some libraries to come along by transitivity, therefore take care to ensure duplication or multiple versions of some dependencies is avoided.<br /> The notebook metadata defines custom dependencies (ai.h2o) and dependencies to not include (because they&rsquo;re already available, i.e. spark, scala and jetty). The custom local repos allow us to define where dependencies are stored locally and thus avoid downloading these each time a notebook is started.</p> <p>&nbsp;</p> <pre> <code>"customLocalRepo": "/tmp/spark-notebook", "customDeps": [ "ai.h2o % sparkling-water-core_2.11 % 2.0.2", "ai.h2o % sparkling-water-examples_2.11 % 2.0.2", "- org.apache.hadoop % hadoop-client % _", "- org.apache.spark % spark-core_2.11 % _", "- org.apache.spark % spark-mllib_2.11 % _", "- org.apache.spark % spark-repl_2.11 % _", "- org.scala-lang % _ % _", "- org.scoverage % _ % _", "- org.eclipse.jetty.aggregate % jetty-servlet % _" ], "customSparkConf": { "spark.ext.h2o.repl.enabled": "false" },</code></pre> <p style="text-align:justify">With these dependencies set, we can start using Sparkling Water and initiate an H2O context from within the notebook.</p> <h2 style="text-align:justify">Benchmark example &ndash; Chicago Crime Scenes:</h2> <p style="text-align:justify">As an example, we can revisit the Chicago Crime Sparkling Water demo. The Spark-Notebook we used for this benchmark can be seen in a read-only mode here.</p> <p style="text-align:justify"><strong>Step 1: </strong>The Three datasets are loaded as spark data frames:</p> <ul> <li style="text-align:justify">Chicago weather data : Min, Max and Mean temperature per day</li> <li style="text-align:justify">Chicago Census data : Average poverty, unemployment, education level and gross income per Chicago Community Area</li> <li style="text-align:justify">Chicago historical crime data : Crime description, date, location, community area, etc. Also contains a flag telling whether the criminal has been arrested or not.</li> </ul> <p style="text-align:justify">The three tables are joined using Spark into a big table with location and date as keys. A view of the first entries of the table are generated by the notebook&rsquo;s automatic rendering of tables (See a sample on the table below).</p> <p>&nbsp;</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/28/spark_tables.png" style="height:100%; width:100%" /></p> <p style="text-align:justify">Geospatial charts widgets are also available in the Spark-Notebook, for example, the 100 first crimes in the table:</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/28/geospatial.png" style="height:100%; width:100%" /></p> <p style="text-align:justify"><strong>Step 2: </strong>We can transform the spark data frame into an H2O Frame and randomly split the H2O Frame into training and validation frames containing 80% and 20% of the rows, respectively. This is a memory to memory transformation, effectively copying and formatting data in the spark data frame into an equivalent representation in the H2O nodes (spawned by Sparkling Water into the spark executors).<br /> We can verify that the frames are loaded into H2O by looking at the H2O Flow UI (available on port 54321 of your spark-notebook installation). We can access it by calling &ldquo;openFlow&rdquo; in a notebook cell.</p> <p>&nbsp;</p> <div> <p style="text-align:center"><img alt="" src="/uploads/uploads/ckeditor/2017/04/11/h2oflow.png" style="width:100%" /></p> </div> <p style="text-align:justify">&nbsp;</p> <p style="text-align:justify"><strong>Step 3: </strong>From the Spark-Notebook, we train two H2O machine learning models on the training H2O frame. For comparison, we are constructing a Deep Learning MLP model and a Gradient Boosting Machine (GBM) model. Both models are using all the data frame columns as features: time, weather, location, and neighborhood census data. Models are living in the H2O context and thus visible in the H2O flow UI. Sparkling Water functions allow us to access these from the SparkContext.</p> <p style="text-align:justify">We compare the classification performance of the two models by looking at the area under the curve (AUC) on the validation dataset. The AUC measures the discrimination power of the model, that is the ability of the model to correctly classify crimes that lead to an arrest or not. The higher, the better.</p> <p style="text-align:justify">The Deep Learning model leads to a 0.89 AUC while the GBM gets to 0.90 AUC. The two models are therefore quite comparable in terms of discrimination power.</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/28/Flow2.png" style="height:100%; width:100%" /></p> <p style="text-align:justify"><strong>Step 4: </strong>Finally, the trained model is used to measure the probability of arrest for two specific crimes:</p> <ul> <li style="text-align:justify">A &ldquo;narcotics&rdquo; related crime on 02/08/2015 11:43:58 PM in a street of community area &ldquo;46&rdquo; in district 4 with FBI code 18. <p>The probability of being arrested predicted by the deep learning model is 99.9% and by the GBM is 75.2%.</p> </li> <li style="text-align:justify">A &ldquo;deceptive practice&rdquo; related crime on 02/08/2015 11:00:39 PM in a residence of community area &ldquo;14&rdquo; in district 9 with FBI code 11. <p>The probability of being arrested predicted by the deep learning model is 1.4% and by the GBM is 12%.</p> </li> </ul> <p style="text-align:justify">The Spark-Notebook allows for a quick computation and visualization of the results:</p> <p><img alt="" src="/uploads/uploads/ckeditor/2017/05/28/spark_notebook.png" style="height:100%; width:100%" /></p> <h2 style="text-align:justify">Summary</h2> <p style="text-align:justify">Combining Spark and H2O within the Spark-Notebook is a very nice set-up for scalable data science. More examples are available in the online viewer. If you are interested in running them, install the Spark-Notebook and look in this repository. From that point , you are on track for enterprise-ready interactive scalable data science.</p> <p style="text-align:justify">&nbsp;</p> <p style="text-align:center">&nbsp;</p> <p><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> loic.quertenmont@gmail.com (Loic Quertenmont)Tue, 11 Apr 2017 06:30:47 +0000http://deeperanalytics.be/blog/2017/04/11/sparkling-water-spark-notebook/Data Science FrameworkR vs Python vs Scala vs Spark vs TensorFlow... The quantitative answer! http://deeperanalytics.be/blog/2017/03/06/r-vs-python-vs-scala-vs-spark-vs-tensorflow-the-quantitative-answer/ <p style="text-align:justify">In this blog, we will finally give an answer to THE question:&nbsp; R, Python, Scala, Spark, Tensorflow, etc...&nbsp; What is the best one to answer data science questions?&nbsp; The question itself is totally absurd, but they are so many people asking it on social network that we find it worth to finally answer the recurrent question using a scientific methodology.&nbsp; At the end of this blog, you will find a quantitative answer comparing the computing time of each language/library for fitting the exact same Generalized Linear Model (GLM).&nbsp; Many features matter in the choice of a language/library, among them , the computing and developing time are for sure very important criteria.</p> <p style="text-align:justify"><!--more--></p> <p><img alt="datasciencelogo" class="alignnone size-full wp-image-1340" src="/uploads/uploads/zinnia/2017/03/06//datasciencelogo.png" style="width:100%" /></p> <p style="text-align:justify">The methodology that we are adopting to answer this famous question is, therefore, considering both the performance of the tool in terms of computing time but also the easiness of the tool in terms of data exploration, model definition etc.&nbsp; The quality of the tool documentation is also an important factor that contributes to faster development time cycles.&nbsp; Finally, scalability of the tool with respect to the size of the dataset is also an important factor in the big data era.</p> <h2 style="text-align:left">Model and Dataset</h2> <p style="text-align:justify">The dataset that we are using in these comparisons is the airline dataset which contains information about flight details since 1987.&nbsp; The dataset is publicly available on the website of the <a href="http://stat-computing.org/dataexpo/2009/the-data.html">American Statistical Association</a>.&nbsp; The dataset is made of 29 columns,&nbsp;7009728 rows and weights 658MB on disk.&nbsp; We will use a 1M flight details from year 2008 as our benchmark dataset.&nbsp; In this post, we will use each tool to put together a model to predict if a flight will arrive on time.&nbsp; The model prediction is based on 9 columns of the input dataset:</p> <ul> <li>Departure date of the flight (Year, Month, DayOfMonth and, DayOfWeek),</li> <li>Departure time</li> <li>Air time</li> <li>Distance</li> <li>Origin airport</li> <li>Destination airport</li> </ul> <div class="cell rendered selected text_cell"> <div class="inner_cell"> <div class="rendered_html text_cell_render"> <p style="text-align:justify">We will use a <a href="https://en.wikipedia.org/wiki/Generalized_linear_model">General Linear Model (GLM)</a> to make the prediction.&nbsp; The GLM is also known as logistics regression, or logit regression.&nbsp; This is quite a basic model which is extensively used in everyday business and which is available in all tools.&nbsp; It therefore offers a nice point of comparison for the types of models that matters today.</p> <p style="text-align:justify">It&#39;s time to remind that the objective of this blog is not to define the most accurate model for predicting if a flight will arrive on time.&nbsp; The main objective of this blog is to offer a quantitative comparison of data science tools in terms of computing performances and coding easiness.&nbsp; In other words, it is on purpose that <strong>we use a simple model with a limited number of inputs.</strong></p> </div> </div> </div> <h2 style="text-align:left">Comparison Procedure</h2> <p style="text-align:justify">All the comparisons are made in <a href="https://jupyter.readthedocs.io/en/latest/index.html">jupyter notebooks</a> using the exact same analysis flow.&nbsp; The notebooks are available on <a href="https://github.com/quertenmont/GLMPerf">GitHub</a> and could be visualized on <a href="http://nbviewer.jupyter.org/github/quertenmont/GLMPerf/tree/master/">NBviewer</a>.&nbsp;&nbsp; <em>Note: If you would like to add comparison to other tools or dataset, feel free to push your results to the GitHub directory.</em>&nbsp; The analysis flow that we repeat for all tools is the following:</p> <ol> <li style="text-align:justify">Import the library</li> <li style="text-align:justify">Load and explore the dataset</li> <li style="text-align:justify">Prepare the dataset for training</li> <li style="text-align:justify">Define and fit the model</li> <li style="text-align:justify">Test the model and Measure the accuracy</li> </ol> <h2 style="text-align:left">Tools (Library/Languages)</h2> <p>R is the installed language in the academic and data science community. SAS is its main commercial competitor. R is considered to be good for pure statistical analysis and it is open source.&nbsp;&nbsp; However, languages like Python and Scala are eating out market share of R.&nbsp; A <a href="http://www.burtchworks.com/2016/07/13/sas-r-python-survey-2016-tool-analytics-pros-prefer/">recent survey by Burtch Works</a> shows how Python gain steam in the analytics community, at the expense of R and proprietary packages like SAS, <a href="http://www.ibm.com/">IBM</a>&lsquo;s SPSS, and <a href="http://www.mathworks.com/">Mathworks</a>&lsquo; Matlab. [caption id=&quot;attachment_1561&quot; align=&quot;aligncenter&quot; width=&quot;300&quot;]<img alt="burtch-works_1-300x212" class="aligncenter size-medium wp-image-1561" src="/uploads/uploads/zinnia/2017/03/06//burtch-works_1-300x212.png?w=300" style="height:212px; width:300px" /> Source Burtch Works[/caption] At the time of writing this blog, we have analyzed the following languages/libraries:</p> <ul> <li>R</li> <li>Python3 + Scikit-learn</li> <li>Python3 + Tensorflow</li> <li>Python3 + Keras</li> <li>Scala + Spark</li> </ul> <p style="text-align:justify">Data manipulations in Python and Scala are done using Pandas and Spark data frame libraries, respectively.&nbsp; Both are inspired by R data frame tool and are therefore very similar.&nbsp; Spark library could also be used in Python, but we prefer to test it using Scala language as this is the native language of the Spark library.&nbsp; We should also highlight that although Spark can be used to analyze small dataset like this one, it was initially designed for the analysis of datasets that are so big that they can not be analyzed efficiently on one single computer.</p> <h2 style="text-align:left">Data Loading and Exploration</h2> <p>In this section, we compare the code complexity for the following tasks:</p> <ol> <li>Load a dataset from a CSV file</li> <li>Print the total number of rows in the dataset</li> <li>Trim the dataset to the first million rows</li> <li>List the column in the dataset</li> <li>Display the first 5 rows on in the jupyter output cell.</li> </ol> <p style="text-align:justify">From the code snippet bellow, we can see that R and python have almost the same syntax.&nbsp; Python (and its libraries Pandas and Numpy are a bit more object oriented which make their usage easier).&nbsp; Spark equivalent code is a bit more complicated but offers the advantage to handle distributed data loading from Hadoop File System (HDFS).</p> <h3>In R:</h3> <pre> <code class="language-r">df = read.csv("2008.csv") #Load data nrow(df) #Get number of rows df = df[0:1000000,] #Keep the first 1M rows names(df) #List columns df[0:5, ] #Get the 5 rows</code></pre> <h3>In Python3:</h3> <pre> <code class="language-python">df = pd.read_csv("2008.csv") #Load data df.shape[0] #Get number of rows df = df[0:1000000] #Keep the first 1M rows df.columns #List columns df[0:5] #Get the 5 rows</code></pre> <h3>In Scala:</h3> <pre> <code class="language-scala">val dffull = sqlContext.read.format("csv") .option("header", true) .option("inferSchema", true) .load("2008.csv") //Load data dffull.count //Get number of rows val df = dffull.sample(false, 1000000.toFloat/count) //Keep the first 1M rows df.printSchema() //List columns df2.show(5) //Get the 5 rows</code></pre> <p>&nbsp;</p> <h2 style="text-align:left">Data Preparation for Model training</h2> <p>In this section, we compare the code complexity for selecting the columns of interest for our model, encode categorical variables and split the dataset into a training sample and a testing sample.</p> <p style="text-align:justify">From the code snippet bellow, we can see that R and python have again almost the same syntax.&nbsp; Spark data processing is a bit different.&nbsp; It relies on the <a href="https://spark.apache.org/docs/latest/ml-pipeline.html">Spark ML Pipelines</a> mechanism which allows better optimization of distributed calculus.</p> <h3>In R:</h3> <pre> <code class="language-r">#drop rows where delay column is na df = df[is.na(df$ArrDelay)==0,] #turn label to numeric df["IsArrDelayed" ] &lt;- as.numeric(df["ArrDelay"]&gt;0) #mark as categorical df["Origin" ] &lt;- model.matrix(~Origin , data=df) df["Dest" ] &lt;- model.matrix(~Dest , data=df) #split the dataset in two parts trainIndex = sample(1:nrow(df), size = round(0.8*nrow(df)), replace=FALSE) train = df[ trainIndex, ] test = df[-trainIndex, ]</code></pre> <h3>In Python3:</h3> <pre> <code class="language-python">#drop rows where delay column is na df = df.dropna(subset=["ArrDelay"]) #turn label to numeric df["IsArrDelayed" ] = (df["ArrDelay"]&gt;0).astype(int) #Mark as categorical (replace by one hot encoded version) df = pd.concat([df, pd.get_dummies(df["Origin"], prefix="Origin")], axis=1); df = pd.concat([df, pd.get_dummies(df["De, axis=1); #split the dataset in two parts train = df.sample(frac=0.8) test = df.drop(train.index)</code></pre> <h3>In Scala:</h3> <pre> <code class="language-scala">//build a pipeline to turn categorical variables to encoded version //and to build a feature vector concatenating all training column into a vector val OriginIndexer = new StringIndexer() .setInputCol("Origin") .setOutputCol("OriginIndex") val OriginEncoder = new OneHotEncoder() .setInputCol("OriginIndex") .setOutputCol("OriginVec") val DestIndexer = new StringIndexer() .setInputCol("Dest") .setOutputCol("DestIndex") val DestEncoder = new OneHotEncoder() .setInputCol("DestIndex") .setOutputCol("DestVec") val Assembler = new VectorAssembler() .setInputCols(Array("Year","Month", "DayofMonth" ,"DayOfWeek", "DepTime", "AirTime", "Distance", "OriginVec", "DestVec")) .setOutputCol("Features") val pipeline = new Pipeline() .setStages(Array(OriginIndexer, OriginEncoder, DestIndexer, DestEncoder, Assembler)) //Transform the dataset using the above pipeline val Preparator = pipeline.fit(df2) val dfPrepared = Preparator.transform(df2).cache() //Split the dataset in two parts val Array(train, test) = dfPrepared.randomSplit(Array(0.8,0.2))</code></pre> <h3 style="text-align:left">&nbsp;</h3> <h2 style="text-align:left">Model building and training</h2> <p style="text-align:justify">In this section, we jump into the heart of the statistical part of the code.&nbsp; Although, The length of the code to define and use a GLM model is quite small for most of the tools, the time needed to execute these few lines of code can be quite long depending on the library (and it&#39;s underlying optimization), on the model complexity and on the size of the dataset itself.&nbsp; Fixing the free parameters of a GLM model requires iterating over the dataset until all free parameters are converging to a unique value.&nbsp; This procedure is often referred to as the fitting of the model.</p> <p style="text-align:justify">They are generally several library options available for fitting a model in a given language.&nbsp; This is particularly true in Python for which new data science and deep learning libraries are developed every day.&nbsp; Among those, <a href="http://scikit-learn.org/stable/">scikit-learn</a> is the reference for many years for all data science algorithms.&nbsp; Deep learning libraries like <a href="https://www.tensorflow.org/">Google Tensorflow</a> and <a href="https://keras.io/">Keras</a> are also gaining in popularity and offers the possibility to exploit the Graphical Processing Unit (GPU) for faster model fitting.&nbsp; Keras uses either Tensorflow (or Theano) as a back-end for the model fitting but it makes the programming a bit easier for common statistical model and algorithms.</p> <p style="text-align:justify"><em>Note:&nbsp; For Tensorflow, we need to decompose the GLM model into simple matrix operations, so the code is a bit more lengthy.&nbsp; For those who need a reminder, the GLM model has linear logits which are linear with respect to the model features X.&nbsp; Logits = (X*W)+B = (Features * Coefficients) + Bias.&nbsp; The model predictions are defined as the sigmoid of the logits.&nbsp; The model loss function (used to optimize the model parameters W and B) is a logistic function.</em></p> <p style="text-align:justify">In the following, you should pay attention at the code complexity, but also at the time it took for fitting the model.&nbsp; The model predicting power will be discussed in the section.</p> <p style="text-align:justify">All test were run on a dataset of approximately 80% x 1M rows and on the same computer powered by an Intel I7-6700K&nbsp; @4.0GHz (8cores) and a GTX970 with 4GB GPU.&nbsp; The GPU is only used in Tensorflow/Keras benchmarks.&nbsp; Spark was used in a local model, so it uses the 8 cores of the processor, but nothing more.</p> <h3>In R: The model fitting took <u><strong>~19min</strong></u></h3> <pre> <code class="language-r">#define the model and fit it model &lt;- glm(IsArrDelayed ~ Year + Month + DayofMonth + DayOfWeek + DepTime + AirTime + Origin + Dest + Distance,data=train,family = binomial)</code></pre> <h3>In Python3 with Scikit-learn: The model fitting took <u><strong>~13sec</strong></u></h3> <pre> <code class="language-python">#define the model feature columns and the label column OriginFeatCols = [col for col in df.columns if ("Origin_" in col)] DestFeatCols = [col for col in df.columns if ("Dest_" in col)] features = train[["Year","Month", "DayofMonth" ,"DayOfWeek", "DepTime", "AirTime", "Distance"] + OriginFeatCols + DestFeatCols ] labels = train["IsArrDelayed"] #define the model per itself (C is the inverse of L2 regularization strength model = LogisticRegression(C=1E5, max_iter=10000) #fit the model model.fit(features, labels) </code></pre> <h3>In Python3 with Tensorflow: The model fitting took <u><strong>~11sec</strong></u></h3> <pre> <code class="language-python">featureSize = features.shape[1] labelSize = 1 training_epochs = 25 batch_size = 2500 #Define the model computation graph graph = tf.Graph() with graph.as_default(): # tf Graph Input LR = tf.placeholder(tf.float32 , name = 'LearningRate') X = tf.placeholder(tf.float32, [None, featureSize], name="features") # features Y = tf.placeholder(tf.float32, [None, labelSize], name="labels") # training label # Set model weights W = tf.Variable(tf.random_normal([featureSize, labelSize],stddev=0.001), name="coefficients") B = tf.Variable(tf.random_normal([labelSize], stddev=0.001), name="bias") # Construct model logits = tf.matmul(X, W) + B with tf.name_scope("prediction") as scope: P = tf.nn.sigmoid(logits) # Cost function and optimizer (Minimize error using cross entropy) L2 = tf.add_n([tf.nn.l2_loss(v) for v in tf.trainable_variables()]) cost = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(targets=Y, logits=logits) ) + 1E-5*L2 optimizer = tf.train.AdamOptimizer(LR).minimize(cost) # Initializing the variables init = tf.initialize_all_variables() #Fit the model (using a training cycle with early stopping) avg_cost_prev = -1 for epoch in range(training_epochs): avg_cost = 0. total_batch = int(features.shape[0]/batch_size) # Loop over all batches for i in range(total_batch): batch_xs = featuresMatrix[i*batch_size:(i+1)*batch_size]#features[i*batch_size:(i+1)*batch_size].as_matrix() batch_ys = labelsMatrix[i*batch_size:(i+1)*batch_size]#labels [i*batch_size:(i+1)*batch_size].as_matrix().reshape(-1,1) #set learning rate learning_rate = 0.1 * pow(0.2, (epoch + float(i)/total_batch)) # Fit training using batch data _, c = sess.run([optimizer, cost], feed_dict={X: batch_xs, Y: batch_ys, LR:learning_rate}) # Compute average loss avg_cost += c / total_batch #check for early stopping if(avg_cost_prev&gt;=0 and (abs(avg_cost-avg_cost_prev))&lt;1e-4): break else: avg_cost_prev = avg_cost</code></pre> <h3>In Python3 with Keras: The model fitting took <u><strong>~55sec</strong></u></h3> <pre> <code class="language-python">featureSize = features.shape[1] labelSize = 1 training_epochs = 25 batch_size = 2500 from keras.models import Sequential from keras.layers import Dense, Activation from keras.regularizers import l2, activity_l2 from sklearn.metrics import roc_auc_score from keras.callbacks import Callback from keras.callbacks import EarlyStopping #DEFINE A CUSTOM CALLBACK class IntervalEvaluation(Callback): def __init__(self): super(Callback, self).__init__() def on_epoch_end(self, epoch, logs={}): print("interval evaluation - epoch: %03d - loss:%8.6f" % (epoch, logs['loss'])) #DEFINE AN EARLY STOPPING FOR THE MODEL earlyStopping = EarlyStopping(monitor='loss', patience=1, verbose=0, mode='auto') #DEFINE THE MODEL model = Sequential() model.add(Dense(labelSize, input_dim=featureSize, activation='sigmoid', W_regularizer=l2(1e-5))) model.compile(optimizer='Adam', loss='binary_crossentropy', metrics=['accuracy']) #FIT THE MODEL model.fit(featuresMatrix, labelsMatrix, batch_size=batch_size, nb_epoch=training_epochs,verbose=0,callbacks=[IntervalEvaluation(),earlyStopping]);</code></pre> <h3>In Scala: The model fitting took <u><strong>44sec</strong></u></h3> <pre> <code class="language-scala">//Define the model val lr = new LogisticRegression() .setMaxIter(10) .setRegParam(0.001) .setLabelCol("IsArrDelayed") .setFeaturesCol("Features") //Fit the model val lrModel = lr.fit(train)</code></pre> <h3 style="text-align:left">&nbsp;</h3> <p style="text-align:justify">As for the other parts, R and python with the (almost native) scikit-learn library are very similar in terms of code complexity.&nbsp; What is quite astonishing is the huge difference in computing time in favor of python.&nbsp; A 15 sec python training translate to a 19 min equivalent training in R.&nbsp; <strong>R is ~90 times slower than python with scikit-learn</strong> and 25 times slower than Spark.</p> <p style="text-align:justify">The fact that spark is a bit slower than python is expected here because Spark communication among each computing nodes is slowing down the process a bit.&nbsp; Here the computing nodes correspond to the 8 cores of the same processor, but still, the communication unit of spark is still eating up a bit of the resource.&nbsp; Spark would start to show an advantage for much larger datasets (not fitting entirely in the computer RAM) or when used on a computing cluster made of dozens of computing nodes or more.</p> <p style="text-align:justify">Similarly, Keras seems slower than Tensorflow alone.&nbsp; We imagine that the cause is also due to a sort of overhead in the communication between Keras front-end and (Tensorflow) backend.&nbsp; This overhead is probably negligible for complex deep learning models with a dozen of layers or even more which are very different from the extremely simple GLM model that we are using for this test.&nbsp; Moreover, we limited out GLM Tensorflow model implantation to its most minimal form.&nbsp; It is, therefore, hard for Keras to do better.</p> <p style="text-align:justify">Tensorflow computing time is almost identical to what we obtained with scikit-learn despite the usage of a GPU with more than 1664 CUDA computing cores.&nbsp; This is again due to the simplicity of the model.&nbsp; The time spent in the model optimization during the training iteration is actually going very fast.&nbsp; The vast majority of the time is actually spent in transferring the training data to the graphic card and gathering back the results from the GPU.&nbsp; More complex models&nbsp;would, therefore, be trained within basically the same amount of time given that the bottleneck here is not the computing power.&nbsp; They are better Tensorflow implantation that could solve this issue and reduce the training time.&nbsp; One of them is to use input <a href="https://www.tensorflow.org/api_guides/python/io_ops#Queues">Queues</a>. But we have not explored this solution for this blog.</p> <h2 style="text-align:left">Model testing and Accuracy</h2> <p style="text-align:justify">In this section, we show compare code snippets to get the model prediction on the testing dataset, to draw the model <a href="https://en.wikipedia.org/wiki/Receiver_operating_characteristic">ROC curve</a>, and to get the model Area Under the Curve (AUC) which is a good indicator of the model classification performance.&nbsp; The higher the AUC the better.&nbsp; But since all the models are using the same input features and the same dataset, we expect the AUC of all the model implantations to be identical.&nbsp; There might be some small difference due to the randomness of the optimization process itself or due to slightly different stopping conditions.</p> <h3>In R: AUC=0.706</h3> <pre> <code class="language-r">#Get the predictions test["IsArrDelayedPred"] &lt;- predict(model, newdata=test, type="response") #Compare prediction with truth and draw the ROC curve pred &lt;- prediction(test$IsArrDelayedPred, test$IsArrDelayed) perf &lt;- performance(pred, measure = "tpr", x.measure = "fpr") plot(perf, col=rainbow(10)) #Get the AUC AUC = performance(pred, measure = "auc")@y.values&lt;/pre&gt; &lt;h3&gt;In Python3: with Scikit-learn: AUC=0.702 with Tensorflow: AUC=0.699 with Keras: AUC=0.689&lt;/h3&gt; &lt;pre&gt; #Get the predictions testFeature = test[["Year","Month", "DayofMonth" ,"DayOfWeek", "DepTime", "AirTime", "Distance"] + OriginFeatCols + DestFeatCols ] test["IsArrDelayedPred"] = model.predict( testFeature.as_matrix() ) #Compare prediction with truth and draw the ROC curve fpr, tpr, _ = roc_curve(test["IsArrDelayed"], test["IsArrDelayedPred"]) plt.figure() plt.plot(fpr, tpr, color='darkorange', lw=4, label='ROC curve') #Get the AUC AUC = auc(fpr, tpr)</code></pre> <h3>In Scala with Spark: AUC=0.645</h3> <pre> <code class="language-python">//Get the predictions val testWithPred = lrModel.transform(test.select("IsArrDelayed","Features")) //Compare prediction with truth and draw the ROC curve val trainingSummary = lrModel.evaluate(test) val binarySummary = trainingSummary.asInstanceOf[BinaryLogisticRegressionSummary] val roc = binarySummary.roc plotly.JupyterScala.init() val fpr = roc.select("FPR").rdd.map(_.getDouble(0)).collect.toSeq; val tpr = roc.select("TPR").rdd.map(_.getDouble(0)).collect.toSeq; plotly.Scatter(fpr, tpr, name = "ROC").plot(title = "ROC Curve") //Get the AUC println(s"areaUnderROC: ${binarySummary.areaUnderROC}")</code></pre> <p style="text-align:justify">All implantations are getting quite similar AUC values.&nbsp; Scala with Spark is a bit behind, but the model parameters have not been tuned at all.&nbsp; We could certainly improve this results by tuning the model convergence, regularization and early stopping parameters of the model training.&nbsp; As the score still remain relatively close to the other implantation, we consider this result as satisfactory for this comparison blog.</p> <h2 style="text-align:left">Summary</h2> <p style="text-align:justify">&nbsp;In summary,&nbsp; we have shown that although code complexity is very similar between R and python implantations of the GLM model, the computing time necessary for training the model is significantly higher in the case of the R implantation.&nbsp; The accuracy of the model itself is about the same.&nbsp; It is clear that R paved the way to modern statistical and data science toolkits, but unfortunately he seems to be left behind compared to more modern framework like Python or Spark.&nbsp; We would therefore recommend to move your data science framework to the Python language which offers much better performances than R,&nbsp; with a coding style very similar to what you are used to do with R data frames.&nbsp; You also get the extra advantage to benefits from the developments of the deep-learning libraries that are produced daily in python.&nbsp; Those brings marginal improvement in the case of simple models like GLM, but seriously boost your performance in the case of more complex models (i.e. several computation layers).</p> <table> <tbody> <tr> <td style="text-align:center">&nbsp;</td> <td><strong>Code Complexity</strong></td> <td><strong>Computing Time (sec)</strong></td> <td><strong>AUC</strong></td> <td><strong>Documentation Quality</strong></td> <td><strong>Additional Remarks</strong></td> </tr> <tr> <td><strong>R</strong></td> <td>*</td> <td>1140</td> <td>0.71</td> <td><a href="https://www.rdocumentation.org/">GOOD</a></td> <td>Much slower</td> </tr> <tr> <td><strong>Python3</strong><strong> </strong><strong>Scikit-learn</strong></td> <td>*</td> <td>13</td> <td>0.70</td> <td><a href="http://scikit-learn.org/stable/">EXCELLENT</a></td> <td><strong>The winner for GLM </strong></td> </tr> <tr> <td><strong>Python3</strong> <strong>Tensorflow</strong></td> <td>**</td> <td>11</td> <td>0.70</td> <td><a href="https://www.tensorflow.org/">GOOD</a></td> <td>Exploit GPU</td> </tr> <tr> <td><strong>Python3</strong><strong> </strong><strong>Keras</strong></td> <td>*</td> <td>55</td> <td>0.69</td> <td><a href="https://keras.io/">GOOD</a></td> <td>Exploit GPU; Good for complex models</td> </tr> <tr> <td><strong>Scala</strong><strong> </strong><strong>Spark</strong></td> <td>**</td> <td>44</td> <td>0.65</td> <td><a href="http://spark.apache.org/docs/latest/ml-guide.html">GOOD</a></td> <td>Good for large dataset</td> </tr> </tbody> </table> <p><em>Note: We&#39;d like to extend this comparison table (so as my <a href="https://github.com/quertenmont/GLMPerf">GitHub</a> directory) with more frameworks.&nbsp; We would, in particular, be interested in comparisons with commercial software (SAS, SPSS, etc.).&nbsp; Contact us if you want to help.</em> &nbsp;</p> <p><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> loic.quertenmont@gmail.com (Loic Quertenmont)Mon, 06 Mar 2017 07:05:23 +0000http://deeperanalytics.be/blog/2017/03/06/r-vs-python-vs-scala-vs-spark-vs-tensorflow-the-quantitative-answer/Data Science FrameworkDeep learningScalable Geospatial data analysis with Geotrellis, Spark, Sparkling-Water and, the Spark-Notebook http://deeperanalytics.be/blog/2017/03/03/scalable-geospatial-data-analysis-with-geotrellis-spark-sparkling-water-and-the-spark-notebook/ <p><em>Note: This blog post was initially written for the blog of <a href="http://www.kensu.io/" target="_blank">Kensu.io</a>, You can either read it here, or continue your reading on its original <a href="https://blog.kensu.io/2017/03/scalable-geospatial-data-analysis-with-geotrellis-spark-sparkling-water-and-the-spark-notebook/" target="_blank">publication page</a>.</em></p> <p style="text-align:justify">This blog shows how to perform scalable geospatial data analysis using <a href="http://geotrellis.io/">Geotrellis</a>, Apache Spark,&nbsp;<a href="http://www.h2o.ai/sparkling-water/">Sparkling-Water</a> and the <a href="http://spark-notebook.io/">Spark-Notebook</a>.</p> <p style="text-align:justify">As a benchmark for this blog, we use the 500 images (and 45GB) dataset distributed by <a href="https://www.kaggle.com/c/dstl-satellite-imagery-feature-detection">Kaggle/DSTL</a>.</p> <p style="text-align:justify">After reading this blog post, you will know how to:</p> <ul> <li style="text-align:justify">Load <a href="https://en.wikipedia.org/wiki/GeoJSON">GeoJSON</a> and <a href="https://en.wikipedia.org/wiki/GeoTIFF">GeoTIFF</a> files with Geotrellis,</li> <li style="text-align:justify">Manipulate/resize/convert geospatial <a href="https://en.wikipedia.org/wiki/GIS_file_formats#Raster">rasters</a> using Geotrellis,</li> <li style="text-align:justify">Distribute geospatial pictures analysis on a spark cluster,</li> <li style="text-align:justify">Display geospatial tiles in the Spark-Notebook,</li> <li style="text-align:justify">Create multispectral histogram from a distributed image dataset,</li> <li style="text-align:justify">Cluster image pixels based on multi-spectral intensity information,</li> <li style="text-align:justify">Use H2O Sparkling-Water to train a machine learning algorithm on a distributed geospatial dataset,</li> <li style="text-align:justify">Use a trained model to identify objects on large geospatial images,</li> <li style="text-align:justify">How to vectorize object rasters into polygons and save them to distributed (parquet) file systems</li> </ul> <p style="text-align:justify">&nbsp;</p> <h2 style="text-align:justify">A Little Background</h2> <p style="text-align:justify"><a href="http://geotrellis.io/">GeoTrellis</a> is a geographic data processing engine for high-performance GIS applications. It comes with a number of functions to load/save rasters on various file systems (local, S3, HDFS and more), to rasterize polygons, to vectorize raster images, and, to manipulate raster data, including cropping/warping, Map Algebra operations, and rendering operations.<br /> <br /> The <a href="http://spark-notebook.io/">Spark-Notebook</a> is an open source notebook (web-based environment for code edition, execution, and data visualization), focused on Scala and Spark. It is thus well suited for enterprise environments, providing Data Scientists and Data Engineers with a common interactive environment for development and scalable machine learning. &nbsp;The <a href="http://spark-notebook.io/">Spark-Notebook</a> is part of the <a href="http://www.kensu.io/">Adalog suite of Kensu.io</a> which addresses agility, maintainability, and productivity for data science teams. Adalog offers to data scientists a short work cycle to deploy their work to the business reality and offers to managers a set of data governance giving a consistent view on the impact of data activities on the market.</p> <p style="text-align:justify"><a href="http://spark-notebook.io/">Sparkling-Water</a> is the solution to get the best of Spark &ndash; its elegant APIs, RDDs, multi-tenant Context and H2O&rsquo;s speed, columnar-compression and fully-featured Machine Learning and Deep-Learning algorithms in an enterprise-ready fashion</p> <p style="text-align:justify">&nbsp;</p> <h2 style="text-align:justify">The environment</h2> <h3 style="text-align:justify">Installing the Spark-Notebook:</h3> <p style="text-align:justify">Just follow the steps described in the <a href="https://github.com/andypetrella/spark-notebook/blob/master/docs/quick_start.md">Spark-Notebook documentation</a> and in less than 5 minutes you&rsquo;ll have it working locally. &nbsp;For a larger project, you may also be interested in reading the documentation on <a href="https://github.com/andypetrella/spark-notebook/blob/master/docs/clusters_clouds.md">how to connect the notebook to an on-premise or cloud computing cluster</a>.</p> <h3 style="text-align:justify">Integrating Geotrellis and Sparkling-Water:</h3> <p style="text-align:justify">In order to integrate Geotrellis and Sparkling-Water with the Spark-Notebook, we need to tell the notebook to load the library dependencies. &nbsp;After this, Spark will automatically distribute the libraries to the spark executors on the cluster. &nbsp;Possible conflicts caused by different version of spark shipped by the Notebook and Sparkling-Water are handled by editing the notebook meta-data like this:</p> <pre> <code>"customRepos": [ "osgeo % default % http://download.osgeo.org/webdav/geotools/ % maven" ], "customDeps": [ "org.locationtech.geotrellis % geotrellis-spark_2.11 % 1.0.0", "org.locationtech.geotrellis % geotrellis-geotools_2.11 % 1.0.0", "org.locationtech.geotrellis % geotrellis-shapefile_2.11 % 1.0.0", "org.locationtech.geotrellis % geotrellis-raster_2.11 % 1.0.0", "ai.h2o % sparkling-water-core_2.11 % 2.0.3", "- org.apache.hadoop % hadoop-client % _", "- org.apache.spark % spark-core_2.11 % _", "- org.apache.spark % spark-mllib_2.11 % _", "- org.apache.spark % spark-repl_2.11 % _", "- org.scala-lang % _ % _", "- org.scoverage % _ % _", "- org.eclipse.jetty.aggregate % jetty-servlet % _" ], "customSparkConf": { "spark.ext.h2o.repl.enabled": "false", "spark.ext.h2o.port.base": 54321, "spark.app.name": "Notebook", }, </code></pre> <p style="text-align:justify">After this, we are done with setting up the environment and we can start using the notebook to answer business/data science questions.</p> <h2 style="text-align:justify">Benchmark example</h2> <p style="text-align:justify">The notebooks we used to explore this dataset are visible <a href="https://viewer.kensu.io/notebooks/Geotrellis">here</a> (read-only mode). &nbsp;The first one is used to explore the training dataset and perform machine-learning training. &nbsp;The second notebook is used to predict the object class types on the entire dataset. &nbsp;In this blog, we are only focusing on some specific parts of these notebooks. &nbsp;The files can also be downloaded from <a href="https://github.com/kensuio/public-notebooks/tree/master/Geotrellis">GitHub</a>.</p> <h3 style="text-align:justify"><br /> 1) Description of the DSTL/Kaggle Dataset</h3> <p style="text-align:justify">The goal of the competition is to detect and classify the types of objects found in the image dataset. &nbsp;The full description of the competition and its dataset are available<a href="https://www.kaggle.com/c/dstl-satellite-imagery-feature-detection/data"> on the Kaggle website</a>. &nbsp;Below is a short summary of the part of interest for this blog:</p> <p style="text-align:justify">DSTL provides 1km x 1km satellite images in both 3-band and 16-band GeoTIFF formats. The images are coming from the WorldView 3 satellite sensor. &nbsp;In total, there are 450 images of which 25 have training labels.</p> <p style="text-align:justify">The DSTL/Kaggle data that we are using consist of:</p> <ul> <li style="text-align:justify"><strong>three_band</strong>: The 3-band images are the traditional RGB natural color images.<strong>It is labeled as &ldquo;R</strong>&rdquo; and has an intensity resolution of 11-bits/pixel and a spatial resolution of 0.31m.</li> <li style="text-align:justify"><strong>sixteen-band</strong>: The 1+16-band images contain spectral information by capturing wider wavelength channels. <ul> <li>The 1 Panchromatic band (450-800 nm) has an intensity resolution of 11-bits/pixel and a spatial resolution of 0.31m. &nbsp;<strong>It is labeled &ldquo;P&rdquo;</strong>.</li> <li>The 8 Multispectral bands from 400 nm to 1040 nm (red, red edge, coastal, blue, green, yellow, near-IR1 and near-IR2) has an intensity resolution of 11-bits/pixel and a spatial resolution of 1.24m. &nbsp;<strong>It is labeled &ldquo;M&rdquo;</strong>.</li> <li>The 8 short-wave infrared (SWIR) bands (1195 &ndash; 2365 nm) has an intensity resolution of 14-bits/pixel and a spatial resolution of 7.5m. &nbsp;<strong>It is labeled &ldquo;A&rdquo;</strong>.</li> </ul> </li> <li style="text-align:justify"><strong>grid_sizes.csv</strong>: the sizes of grids for all the images</li> <li style="text-align:justify"><strong>train_geojson</strong>: GeoJSON files containing identified (multi-)polygons on the 25 training images. There are polygons of each of the 10 possible object class types used in this competition: <ul> <li>Buildings</li> <li>Misc. Manmade structures</li> <li>Road</li> <li>Track</li> <li>Trees</li> <li>Crops</li> <li>Waterway</li> <li>Standing water</li> <li>Large Vehicle</li> <li>Small Vehicle.</li> </ul> </li> </ul> <h3 style="text-align:justify">2) GeoTIFF loading and image exploration</h3> <p style="text-align:justify">For this benchmark, the easiest is to build a Spark RDD in which the elements contain all the spectral information for a given image &mdash; the R, P, M and A (3+1+8+8) bands. &nbsp;</p> <p style="text-align:justify">Since the intensity resolution differs in each band, we convert the images to a float format where the intensity of each pixel is in the range from 0 to 1.</p> <p style="text-align:justify">Similarly, we resize all the images to the best space resolution (R and P dimensions) &mdash; approximately 3400 x 3400 pixels. &nbsp;A Bicubic-Spline interpolation is used during this process. Geotrellis functions are very efficient to load, convert and resize pictures. &nbsp;</p> <p style="text-align:justify"><em>Note: actually, R and P dimensions can slightly differ from one picture to another, so we can&rsquo;t resize them all to the same unique dimension.</em></p> <p style="text-align:justify">Finally, we align pictures from different bands such that the objects on the images are at the exact same pixel coordinates on all spectral bands. &nbsp;To do so, the resized images from band A, M and P are shifted by a horizontal and vertical offset with respect to the R band. &nbsp;The alignment constants are computing in an external script using the findTransformECC function of openCV which is particularly efficient at finding these offsets.</p> <pre> <code class="language-scala">val processedTiffM = MultibandGeoTiff(path+"_M.tif") .tile.convert(DoubleConstantNoDataCellType).mapDouble{ (b,p) = p/2048.0} .resample(new Extent(0,0,nCols, nRows), nColsNew, nRowsNew, CubicSpline).</code></pre> <p style="text-align:justify">We do this for all the images of the training set and show the resulting images (from the R band tile). &nbsp;These miniatures allow us to pick-up an interesting benchmark example that we can use for detailed studies. &nbsp;In the following, we will use the image labeled <em>6120_2_2</em>. &nbsp;It shows a village in a dusty desert.</p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/Miniatures.png" style="height:100%; width:100%" /></p> <p style="text-align:justify">For the selected image, we can show the intensity of each spectral band. &nbsp;We can immediately see that each spectral band contains complementary information of the same geographical location. &nbsp;</p> <p style="text-align:justify">From this step, it&rsquo;s already obvious that we can exploit the difference of the band intensity to categorize objects on the ground.</p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/spectral.png" style="height:100%; width:100%" /></p> <h3 style="text-align:justify">3) Object Polygons</h3> <p style="text-align:justify">For the images in the training set, DSTL/Kaggle also provides GeoJSON files indicating the location of identified objects on the ground. &nbsp;The JSON files contain coordinates of polygon vertices associated with the objects of a given class type. &nbsp;</p> <p style="text-align:justify">The files are easily loaded thanks to Geotrellis (<em>GeoJson.fromFile</em>) functions. &nbsp;The library also offers functions to make a raster image out of the vectorial polygon information. &nbsp;We use the function &ldquo;<em>PolygonRasterizer.foreachCellByPolygon</em>&rdquo; to create masks of the various object class types visible on the images. &nbsp;</p> <p style="text-align:justify">The image <em>grid_sizes</em> are used in this process in order to project the vectorial polygons on the raster images. &nbsp;</p> <p style="text-align:justify">In the figure below, identified objects are shown as black pixels.</p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/tags.png" style="height:100%; width:100%" /></p> <p style="text-align:justify">We can use those masks to select pixels associated to specific object class types from the 20 available spectral bands. &nbsp;These pixels will be used to build a machine learning algorithm trained to identify specific objects. &nbsp;</p> <p style="text-align:justify">Before that, we will zoom in the top-left corner of the picture and overlay (in blue) the object polygons to the RGB picture in order to observe the level of details of the polygons (and of the picture). &nbsp;We can also observe that some pixels belong to more than one object class. &nbsp;Note for instance that the &ldquo;trees&rdquo; in the left part of the pictures are also part of a &ldquo;crop&rdquo;. &nbsp;The prediction algorithm that we are designing will, therefore, need to achieve multi-class tagging with the same level of details than what we see here.</p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/tags2.png" style="height:100%; width:100%" /></p> <h3 style="text-align:justify">4) Spectral Histograms per object types</h3> <p style="text-align:justify">At this stage, it is interesting to see how the raw image histogram (per spectral band) by the object type masks. &nbsp;This tells us which bands are useful to discriminate some object types. &nbsp;</p> <p style="text-align:justify">For instance, We see that the near-IR2 band is particularly good at discriminating water, building, and crops from the rest. &nbsp;Other bands might be more performant for other objects.</p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/SpectralHisto.png" style="height:100%; width:100%" /></p> <p style="text-align:justify">The figure below shows the histogram for the 8 bands of the &lsquo;M&rsquo; GeoTIFF</p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/SpecralHistoM.png" style="height:100%; width:100%" /></p> <h3 style="text-align:justify">5) Model Learning</h3> <p style="text-align:justify">We will H2O Sparkling-Water to train <a href="https://en.wikipedia.org/wiki/Gradient_boosting">Gradient Boosted Machine</a> (GBM) models that discriminate pixels of one object class type from the rest, using a <a href="https://en.wikipedia.org/wiki/Multiclass_classification#One-vs.-rest">one-vs-all</a> approach. &nbsp;As we have 10 possible class types, we train 10 different algorithms returning the probability that a pixel belongs to a given class type. &nbsp;</p> <p style="text-align:justify"><em>Note: &nbsp;Other approaches might lead to better performances at the cost of higher code complexity and training time.</em></p> <p style="text-align:justify">To train the algorithms on class type, we create a dataset made of 200K randomly chosen pixels belonging to the class type and of 200K pixels not belong to it. &nbsp;For each pixel, we collect the intensity of the 3+1+8+8 spectral bands. This dataset is converted into an H2O Frame and split into a training dataset (90%) used to train the GBM algorithm and a validation dataset (10%) that is used to evaluate the performance of the trained algorithm to identify object type of the pixels. &nbsp;</p> <p style="text-align:justify">The training is distributed on a Spark cluster via Sparkling-Water. &nbsp;The GBM is made of 100 trees with a maximal depth of 15. &nbsp;</p> <p style="text-align:justify">The performance of the model is obtained from the <a href="https://en.wikipedia.org/wiki/Receiver_operating_characteristic#Area_under_the_curve">Area Under Curve (AUC)</a> computed on the validation dataset. &nbsp;The AUC is at best equal to 1.0 and a model is generally considered satisfactory when it has an AUC above 0.8. &nbsp;The operation is repeated for each class type. &nbsp;The table below summarizes the model AUC of each object class type.</p> <table align="center"> <tbody> <tr> <td style="text-align:center"><strong>Object Type</strong></td> <td style="text-align:center"><strong>Model AUC</strong></td> </tr> <tr> <td style="text-align:center">Buildings</td> <td style="text-align:center">0.992441</td> </tr> <tr> <td style="text-align:center">Manmade</td> <td style="text-align:center">0.966026</td> </tr> <tr> <td style="text-align:center">Road</td> <td style="text-align:center">0.997235</td> </tr> <tr> <td style="text-align:center">Track</td> <td style="text-align:center">0.930540</td> </tr> <tr> <td style="text-align:center">Trees</td> <td style="text-align:center">0.960790</td> </tr> <tr> <td style="text-align:center">Crops</td> <td style="text-align:center">0.983181</td> </tr> <tr> <td style="text-align:center">Waterway</td> <td style="text-align:center">0.999718</td> </tr> <tr> <td style="text-align:center">Standing Water</td> <td style="text-align:center">0.999889</td> </tr> <tr> <td style="text-align:center">Large Vehicles</td> <td style="text-align:center">0.999354</td> </tr> <tr> <td style="text-align:center">Small Vehicles</td> <td style="text-align:center">0.997031</td> </tr> </tbody> </table> <p style="text-align:justify">The trained models are saved in the form of <a href="https://github.com/h2oai/h2o-3/blob/master/h2o-docs/src/product/howto/MOJO_QuickStart.md">MOJO</a> files which we could easily import in Scala/Java when we&rsquo;ll want to use them.</p> <h3 style="text-align:justify">6) Pixel Clustering and Model Prediction</h3> <p style="text-align:justify">The previous section closed the model learning part of this analysis. &nbsp;In the next sections, we will use the trained model to identify the objects on raw pictures (for which we don&rsquo;t have the polygons).</p> <p style="text-align:justify"><em>Note: for comparison purposes, we keep using the image labeled 6120_2_2 as the benchmark example.</em></p> <p style="text-align:justify">Running the model prediction on the 11.5M pixels of the 450 images of the dataset is extremely time-consuming. &nbsp;</p> <p style="text-align:justify">Hint: because many pixels on each picture have very similar spectral intensity, we can save a lot of computing time by clustering similar neighboring clusters together and compute the model prediction at the level of the pixel cluster. &nbsp;</p> <p style="text-align:justify">We develop a simple algorithm to aggregate adjacent clusters which have similar spectral information (with a tolerance of 3%). &nbsp;The cluster color is taken as the color average of the constituting pixels. &nbsp;In a second stage, small clusters (&lt;50 pixels) are merged with the surrounding cluster with the closest color. &nbsp;</p> <p style="text-align:justify">Finally, the previously trained models are used to predict the probability that an entire cluster belong to each of the 10 possible object class types.</p> <p style="text-align:justify">The result of this algorithm is shown below for the <em>6120_2_2 image. </em>The first row, shows the full image, while the middle and bottom rows are zoomed in the bottom-left and on the top-left corner of the image respectively. &nbsp;The three columns show, from left to right:</p> <ul> <li style="text-align:justify">The R-band image: the image brightness was increased to better appreciate the objects on the image. &nbsp;This results in some (harmless) color glitches in the overexposed area.</li> <li style="text-align:justify">The identified clusters on the image. &nbsp;Where the cluster are randomly colored using a 256 gray levels palette. &nbsp;In other words, color has no particular meaning here.</li> <li style="text-align:justify">The identified clusters on the image colored according to the most probable class type of the object belonging to the cluster. &nbsp;An eleventh color level is also present for clusters belonging to none of the object class types. &nbsp;On these pictures, we can see that the shape of the objects in the picture is quite visible.</li> </ul> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/Pred1.png" style="height:100%; width:100%" /></p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/Pred2.png" style="height:100%; width:100%" /></p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/Pred3.png" style="height:100%; width:100%" /></p> <h3 style="text-align:justify">7) Mask Predictions</h3> <p style="text-align:justify">From this information, we can create a raster mask per object class type indicating the presence of an object or not. &nbsp;Overlap of object class types is handled by a set of ad hoc rules based on other class types probability for this cluster and for the surrounding ones. &nbsp;</p> <p style="text-align:justify">Example, we know that the chances to find a truck in the middle of a waterway area are null. &nbsp;Similarly, having a tree on top of a road is unlikely. &nbsp;On the contrary, finding a car or a truck on a road is quite probable, so as finding a tree in the middle of a crop.</p> <p style="text-align:justify">These rules are tuned by hand based on what is observed in the training dataset. &nbsp;More sophisticated rules taking, for instance, into account the size of the cluster could also be added. &nbsp;Having a crop cluster made of a few pixel or a car of thousands of pixels are both quite unlikely. &nbsp;But we didn&rsquo;t push the exercise that far for this blog.<br /> We overlay in blue the masks that we obtained on the bottom-left corner of R-band image. &nbsp;These predicted masks are directly comparable with those shown in section 3). &nbsp;From this, we can see that we are doing quite well for identifying Building, Crops, and Trees. &nbsp;For Manmade structures, and vehicles we overpredict quite a bit. &nbsp;And our algorithms has a hard time to make difference between road and tracks in this dusty environment. &nbsp;</p> <p style="text-align:justify"><img alt="" src="/uploads/uploads/ckeditor/2017/05/29/PredTags.png" style="height:100%; width:100%" /></p> <p style="text-align:justify">These issues could be solved by implementing more complex rules for the class overlaps (as discussed earlier) and/or by completing the one-vs-all models by some dedicated <a href="https://en.wikipedia.org/wiki/Multiclass_classification#One-vs.-one">one-vs-one</a> models which could be used to solve the ambiguities between road vs tracks, large vs small vehicles or standing water vs waterway. &nbsp;Again, we didn&rsquo;t push the exercise that far for this introductory blog.</p> <h3 style="text-align:justify">8) Mask Vectorizing</h3> <p style="text-align:justify">In this last step, we convert the object raster masks into a list of polygons. &nbsp;The Geotrellis <em>Tile.toVector</em> function allows doing this quite easily. &nbsp;&nbsp;The image grid_sizes are used in this process in order to translate pixel coordinates into vectorial coordinates on the grid.</p> <p style="text-align:justify">We notice that for complex rasters with holes (i.e. the mask of the crops shown above), the function may have difficulties in identifying the underlying polygons. &nbsp;In this case, we split the raster in 4 quarters and try to vectorize each of the quarters separately. &nbsp;This procedure is applied iteratively until the sub-quarter rasters get to small or the vectorizer succeeds at identifying the polygons.</p> <p style="text-align:justify">Geotrellis also provides high-level functions to manipulate/modify the polygons. &nbsp;We can, for instance, <em>simplify</em> the polygons to make them smoother and reduce their memory/disk footprint.</p> <p style="text-align:justify">Finally, the polygons are saved on disk either in <a href="https://en.wikipedia.org/wiki/GeoJSON">GeoJSON</a> format or in <a href="https://fr.wikipedia.org/wiki/Well-known_text">WKT</a> format.</p> <h2 style="text-align:justify">Summary</h2> <p style="text-align:justify">We have shown how to combine the Spark, Geotrellis, H2O Sparkling-Water and the Spark-Notebook to perform scalable geospatial data analysis. &nbsp;We&rsquo;ve taken an end-to-end benchmark example involving distributed Extract-Transform-Load (ETL) on GeoTIFF and GeoJSON data, Multi-spectral geospatial images analysis, a pixel based object tagger training, and more.</p> <p style="text-align:justify">If you like this blog and are eager to see more details on this GIS benchmark, you should have a look at this <a href="https://viewer.kensu.io/tree/Geotrellis">example repository</a>. &nbsp;Remember that all these notebooks are in read-only mode, so you can only see them. &nbsp;If you want to play with your own example you gonna have to take the 5 min needed to install the Spark-Notebook on your machine.&nbsp;</p> <p style="text-align:right">&nbsp;</p> <p style="text-align:right">&nbsp;</p> <p style="text-align:right">&nbsp;</p> <p style="text-align:right">&nbsp;</p> <p style="text-align:right">&nbsp;</p> <p><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> <p>&nbsp;</p> loic.quertenmont@gmail.com (Loic Quertenmont)Fri, 03 Mar 2017 07:05:22 +0000http://deeperanalytics.be/blog/2017/03/03/scalable-geospatial-data-analysis-with-geotrellis-spark-sparkling-water-and-the-spark-notebook/Business CaseGeoSpatialIdentifying new shop implantation thanks to geo-data analysis http://deeperanalytics.be/blog/2017/02/08/identifying-new-shop-implantation-thanks-to-geo-data-analysis/ <p style="text-align:justify">In this blog, we will see how we can perform geospatial data analysis in order to identify new business opportunities.&nbsp; For this showcase, we will focus on the retail sector and more precisely on the&nbsp;supermarket leading brands in Belgium: Colruyt, Delhaize, Carrefour, and Lidl.&nbsp; We analyzed the location of supermarkets in Brussels, computed the average time travel to the closest supermarket for Brussels neighborhood and see how these four major brands are sharing their market zone among Brussels neighborhood accordingly.&nbsp; We are reusing the techniques detailed in the <a href="/blog/2016/12/14/scrapping-land-invest-data-from-dynamic-web/">Dynamic Web scrapping blog post</a>.&nbsp; The techniques described in this post can be useful for all sorts of B2C companies involved in the retail sector, where competition is generally strong and shop implantation matters.<!--more--></p> <p style="text-align:justify">In a first step, we collect data of supermarket implantation that we can find on Belgian shop indexers (pages d&#39;Or, foursquare, etc.) web pages.&nbsp; To do so, we repeat the procedure described in the&nbsp;<a href="/blog/2016/12/14/scrapping-land-invest-data-from-dynamic-web/">Dynamic Web scrapping&nbsp;blog post</a>.&nbsp; We collect the data from the 19 towns of Brussels.&nbsp; In order to extend the coverage on the west part of Brussels, we also consider the town of Dilbeek.&nbsp; We then cleaned up the list of shops that we extracted in order to consider only those from Colruyt, Delhaize, Carrefour, and Lidl brands.&nbsp; Shops that are too close (&lt;10m) from each other are ignored in order to avoid counting twice the same shop.&nbsp; This can happen if a shop is shared among two towns, if the shop brand or name recently changed or if the indexing isn&rsquo;t perfectly up to date.&nbsp; After the cross-cleaning, we identified 109 supermarkets in the Brussels area.</p> <p style="text-align:justify">We can then translate the shop addresses into a list of GPS coordinates using free services like the geopy geolocator which is doing a pretty good job.</p> <p>[sourcecode language=&quot;python&quot;] #with d a dictionary containing shop information (and in particular the address) loc = geolocator.geocode(d[&quot;address&quot;]) d[&quot;coord&quot;] = [loc.latitude, loc.longitude] #after this, the distance between two shops, d and d2 can be computed as: dist = great_circle(d[&quot;coord&quot;], d2[&quot;coord&quot;]).meters [/sourcecode] We can compute the average latitude and longitude of the 109 shop coordinates that we collected.&nbsp; The average coordinates are used to create a map centered on the region of interest.&nbsp; We use the ipyleaflet widget to create interactive maps into a Jupyter notebook.&nbsp; Now that we have a map, we can display all the identified shops and associated to each of these a color representing the shop brand.</p> <p style="text-align:justify"><img alt="screenshot-from-2017-02-08-14-02-53" class="alignnone size-full wp-image-1284" src="/uploads/uploads/zinnia/2017/02/08//screenshot-from-2017-02-08-14-02-53.png" style="height:400px; width:989px" /></p> <p style="text-align:justify">We can already see that the various brands have quite different implantation strategies in Brussels.&nbsp; Some of them might be due to historical reasons.&nbsp; We can see that there are many Carrefour shops and that those are generally located in streets in the middle of a neighborhood.&nbsp; Those are likely relatively small shops with a local history.&nbsp; On the contrary, we can see that Colruyt and Lidl are generally located close to main roads and axes.&nbsp; Delhaize strategy seems to be somewhere in between.&nbsp; Given these orthogonal strategies, it is not easy to identify which brand is dominating in a specific area.</p> <p style="text-align:justify">In order to better visualize the influence of each brand in Brussels, we can use a grid to split the Brussels map into 150 x 75 small cells.&nbsp; For each cell, we compute what is the average time to travel from that cell to all identified shops.&nbsp; We can then pick-up the closest shop in travel time. We can use the <a href="http://project-osrm.org/">Open Source Routing Machine (OSRM)</a> service to compute the travel time (by car) between two points.&nbsp; There is a public server with a RESTful API that can do such computations for us.&nbsp; Actually, we can do the computation of several points at the same time by providing up to ~100 coordinates through the GET request.&nbsp; The API response takes the form of a JSON file containing, among other things, a time distance matrix to go from the source coordinates to the destination coordinates. Thanks to this (free) API, the task is therefore quite simple.</p> <p style="text-align:justify">We are then just left to draw the cells onto the map with a color indicating the brand of the closest shop.</p> <p style="text-align:justify"><img alt="screenshot-from-2017-02-08-14-05-08" class="alignnone size-full wp-image-1287" src="/uploads/uploads/zinnia/2017/02/08//screenshot-from-2017-02-08-14-05-08.png" style="height:400px; width:985px" /></p> <p style="text-align:justify">From this colored map, we can see that Carrefour is dominating the map.&nbsp; Nonetheless, the other brands also cover significant areas and they do so with much fewer shops.&nbsp; So they might actually be winning the fight of investment versus coverage.</p> <p style="text-align:justify">More interestingly, we can now have a look at the map showing the travel time to the closest shop.&nbsp; Red color indicates small travel time, White color indicates a travel time larger than 5 minutes.&nbsp; The color shade in-between indicates the travel time somewhere between 0 and 5 minutes. This map is very instructive because it allows realizing the importance of the main axes into the implantation strategy of some brands.&nbsp; It also allows spotting Brussels area where all supermarkets require a ride of more than 5 minutes.&nbsp; Those areas are good candidates for new shop implantations as they would almost guarantee to redirect the local population to the new shop brand.</p> <p style="text-align:justify"><img alt="screenshot-from-2017-02-08-14-05-48" class="alignnone size-full wp-image-1290" src="/uploads/uploads/zinnia/2017/02/08//screenshot-from-2017-02-08-14-05-48.png" style="height:401px; width:986px" /></p> <p style="text-align:justify">This is the end of this benchmark demo, but they are several other things that we could do with such geospatial data analysis. &nbsp;Just to cite few of them: we could, for instance, correlate the information to the local census data.&nbsp; This would help identify the area where the local density of population is high and where there are no other shops nearby.&nbsp; We could also correlate these areas with real estate data in order to find good location/price commercial surface for renting.&nbsp; We could take into account the size of the supermarket into the neighborhood sharing map.&nbsp; We could correlate supermarket locations with nearby other types of shops like fuel stations, car store, DIY store, etc. &nbsp;We could build new maps with new shop implantation hypothesis and see how much we could steal business to the competitors. Etc&hellip;&nbsp; As usual, possibilities are endless.</p> <p style="text-align:justify">The code used for this benchmark demo is available in a <a href="http://nbviewer.jupyter.org/github/quertenmont/Notebooks/blob/master/GeoMap.ipynb">notebook format on NBviewer</a>. Unfortunately, it doesn&#39;t handle map properly, but it gives you access to the code to reproduce my analysis. &nbsp;Feel free to play with it.</p> <p style="text-align:justify"><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> loic.quertenmont@gmail.com (Loic Quertenmont)Wed, 08 Feb 2017 07:05:21 +0000http://deeperanalytics.be/blog/2017/02/08/identifying-new-shop-implantation-thanks-to-geo-data-analysis/Business CaseData miningCustomer Analytics, Segmentation and Churn study from Facebook data http://deeperanalytics.be/blog/2017/01/19/customer-analytics-segmentation-and-churn-study-from-facebook-data/ <p style="text-align:justify">In this blog, we will see how we can perform in-depth customer analytics using publicly available inputs from the customers on company Facebook pages.&nbsp; For this showcase, we will focus on the media sector and more precisely on the <a href="http://www.rtlgroup.com">RTL group</a>&nbsp; (leading TV &amp; Radio on the French speaking side of Belgium).&nbsp; We analyzed the behavior of people acting on the Facebook pages of the RTL group and aggregated all available information to perform per-user analytics and predictions.&nbsp; We are reusing the techniques detailed in previously published blog posts on <a href="/blog/2016/12/15/scrapping-social-data-from-facebook/">Facebook Mining</a> and <a href="/blog/2016/12/19/sentiment-analysis-of-french-texts-using-deep-learning-techniques/">Sentiment Analysis</a>.&nbsp; The techniques described in this post can be very useful for all major B2C companies involved in the media, telecoms, retails sectors.<!--more-->For this benchmark example, we will focus on two TV channels (<a href="http://www.rtl.be/tv/rtltvi/">RTL-TVI</a> and <a href="http://www.rtl.be/tv/plugrtl/">Plug RTL</a>) and two radio channels (<a href="http://www.rtl.be/belrtl/index-bel-rtl.htm">Bel RTL</a> and <a href="http://www.radiocontact.be/index-radio-contact.htm">Radio Contact</a>). All these channels are owned by the RTL group and target a specific audience.&nbsp; For instance,&nbsp; plug RTL channel is targeting your adults (age from 15 to 34) while Bel RTL is more focused toward senior people.</p> <p style="text-align:justify">In a first step, we collect data for Facebook pages of these four channels.&nbsp; To do so, we repeat the procedure described in the&nbsp; <a href="/blog/2016/12/15/scrapping-social-data-from-facebook/">Facebook Mining</a> blog post for each of the channels.&nbsp; We collected two months of data in the period ranging from the 22nd of September 2016 to the 22nd of November 2016.&nbsp; We quickly see that there have been many posts from RTL over that period with about 100 likes per posts.&nbsp; Plug-RTL being the exception with a limited number of posts and likes in that period.</p> <p>[gallery ids=&quot;1120,1121,1122,1123&quot; columns=&quot;2&quot; size=&quot;large&quot;]</p> <table border="0" cellpadding="1" cellspacing="1" style="width:100%"> <tbody> <tr> <td><img alt="" src="/uploads/uploads/ckeditor/2017/04/07/likestrend.png" style="width:45%" /><img alt="" src="/uploads/uploads/ckeditor/2017/04/07/likestrend1.png" style="width:45%" /></td> <td>&nbsp;</td> </tr> <tr> <td><img alt="" src="/uploads/uploads/ckeditor/2017/04/07/likestrend2.png" style="width:45%" /><img alt="" src="/uploads/uploads/ckeditor/2017/04/07/likestrend3.png" style="width:45%" /></td> <td>&nbsp;</td> </tr> </tbody> </table> <p style="text-align:justify">Now that we have the data, we can identify who likes each individual Facebook posts and build a collection of Facebook users interacting with these four RTL pages.&nbsp; With just two months of data, we collected about 125 thousand individual Facebook users.&nbsp; Among which about 10% have been acting on at least two of the four pages. When you think that the large majority of Facebook users are using their name as Facebook pseudo, it means that we could actually collect about 125K real people names with just a few lines of code.&nbsp; This is impressive.&nbsp; If you are scared... stop reading here, because we are actually going to do much more than this...</p> <p style="text-align:justify">At this point, we have enough information to start building a per-user dashboard (one per individual users that we have identified).&nbsp; To do so, we need to keep track of what actions each user made on every single post.&nbsp; This adds a little bit of complexity to our &quot;user&quot; database, but actually not that much...</p> <p style="text-align:justify">Many user-centric metrics can then be built out of this user-action-post dataset. &nbsp;A simple set of metrics that we can use is user activity over time and channel, overall user activity per channel, passive (likes) vs active (messages) user actions per channel. &nbsp;Bellow, you can see the user-dashboard result for one random (anonymized) Facebook user (&quot;Dany&quot;) out of our 125K known users.</p> <p><img alt="rtl_dany1" class="alignnone size-full wp-image-1171" src="/uploads/uploads/zinnia/2017/01/19/rtl_dany1.png" style="width:100%" /></p> <p style="text-align:justify">We can also construct more complex metrics by, for instance, analyzing the type of posts the user likes or comments on.&nbsp; We classified the posts in various topic categories and checked on which topics the user is performing actions.&nbsp; We subdivided Facebook posts in 9 categories related to channel programs, channel presenters, miscellaneous, music related, politic related, television series, weather related, movie related and humor related. Then, we counted the number of messages of a given type a specific user liked for each channel, and use that to build the DNA profile of a specific customer. &nbsp;Obviously, categories can be customized depending on your business needs and type of questions&nbsp;you are willing to answer.&nbsp; On the figure bellow, extracted from Dany&#39;s dashboard, the numbers in parenthesis correspond to the number of posts that Dany liked among the total number of RTL posts in that category over the considered period. &nbsp;The color bars represent the&nbsp;ratio of this two numbers (ranging from 0 to 1).</p> <p><img alt="rtl_dany2" class="alignnone size-full wp-image-1191" src="/uploads/uploads/zinnia/2017/01/19/rtl_dany2.png" style="width:100%" /></p> <p style="text-align:justify">Now, it should be clear, that we can perform user segmentation or user 360&deg; analysis&nbsp;by comparing Dany&#39;s DNA profile to the other users we identified.&nbsp; The simplest method would be to perform a K-means clustering in view of grouping users similar interests.</p> <p style="text-align:justify">We&nbsp;can also analyze the messages posted on Facebook by the customers.&nbsp; The sentimental analysis algorithm that we developed in a <a href="/blog/2016/12/19/sentiment-analysis-of-french-texts-using-deep-learning-techniques/">previous post</a> is particularly interesting as it allows us to spot negative messages from unsatisfied customers.&nbsp; Identifying such messages allow us to engage communication with these unsatisfied customers before they change channel/brand (churn prevention) or before they spread their unsatisfactoriness with other customers.&nbsp; This could potentially have a significant impact on the image of your company and on your communication strategy.&nbsp; The faster we react on the Facebook page, the better we preserve the image of the company.</p> <p style="text-align:justify">The figure bellow shows the 5 latest reactions from Dany to posts on RTL Facebook channel pages. &nbsp;We can see that overall, Dany clearly likes the programs on RTL channels. &nbsp;He is a fully satisfied customer.</p> <p><img alt="rtl_dany3" class="alignnone size-full wp-image-1226" src="/uploads/uploads/zinnia/2017/01/19/rtl_dany3.png" style="width:100%" /></p> <p style="text-align:justify">In comparison, we can take a look at the similar metric for another user. &nbsp; Anne commented 71 times over a period of just two months.&nbsp; We see that she is a much less satisfied user and&nbsp;many of her messages are actually questions that would require an answer from RTL side. &nbsp;Most of these questions are very easy to answer and would help to turn Anne into a satisfied user. &nbsp;Note that in the dashboard, we require a confidence level on the sentence polarity of more than 75%.&nbsp; Messages bellow this threshold are marked as &quot;unclear&quot; polarity.</p> <p><img alt="rtl_anne" class="alignnone size-full wp-image-1244" src="/uploads/uploads/zinnia/2017/01/19/rtl_anne.png" style="width:100%" /></p> <p style="text-align:justify">They are several other things that we can do with such cross-channels Facebook data analysis. &nbsp;We could, for instance, prevent further churn by comparing the activity of the customers on competitor (RTBF, vivacit&eacute;, etc.) Facebook pages. &nbsp;We could&nbsp;enhance user segmentation by looking at pages of other companies:&nbsp; We can identify the cooking lovers by checking their activity on leader cooking pages, etc.&nbsp; We could perform the study over a much longer time period in order to identify trends and provide feedback/suggestions for a better communication with the customers. &nbsp;We could cross Facebook user database with other databases in order to find the mail or physical address of the customers and possibly engage communication, sending discounts, etc. &nbsp;Possibilities are basically infinite...</p> <p><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> loic.quertenmont@gmail.com (Loic Quertenmont)Thu, 19 Jan 2017 07:05:21 +0000http://deeperanalytics.be/blog/2017/01/19/customer-analytics-segmentation-and-churn-study-from-facebook-data/Business CaseData miningDeep learningNatural Language ProcessingSentiment Analysis of French texts using deep learning techniques http://deeperanalytics.be/blog/2016/12/19/sentiment-analysis-of-french-texts-using-deep-learning-techniques/ <p style="text-align:justify">In this blog, we will see how deep learning techniques&nbsp;(Recurrent Neural Network, RNN and/or Convolutional Neural Network, CNN) can be used to&nbsp;determine the sentiment polarity of a written&nbsp;text. &nbsp;This is call &quot;sentiment analysis&quot; and it&#39;s very useful to enhanced the communication with your customers. &nbsp;Such algorithms are&nbsp;typically used to analyze emails, website or even <a href="/blog/2016/12/15/scrapping-social-data-from-facebook/">Facebook posts</a>&nbsp;where your customers may talk about your products. Thanks to this, you can prioritize your answers and react faster to the unsatisfied customers...<!--more-->The web is full of blogs&nbsp;explaining how to perform polarity analysis on English texts. Unfortunately, most of these tutorials are not very useful&nbsp;to&nbsp;analyze text in other languages or contexts. &nbsp;These blogs either relies on:</p> <ul> <li style="text-align:justify">Classical Natural Language Processing (NLP) libraries trained by others for the sentiment analysis, like <a href="http://www.nltk.org/">NLTK</a>, <a href="http://polyglot.readthedocs.io/en/latest/index.html">Polyglot </a>or the <a href="http://nlp.stanford.edu/">Standard&nbsp;NLP</a>. &nbsp;And those are often limited to a specific list of languages and/or quickly become inefficient for specific (i.e. business) text context.</li> <li style="text-align:justify">Deep learning techniques using always the same <a href="http://www.imdb.com/interfaces">IMDB dataset</a> of movie reviews in English.&nbsp; Actually, this dataset can not even be used for commercial application (see the license on the IMDB website) and it can therefore not be used for business purposes. &nbsp;Moreover, depending on the type of text you are planning to analyze, it&#39;s certainly&nbsp;better to take a&nbsp;dataset which is closer to your business context. &nbsp;For instance, a movie reviews dataset won&#39;t be ideal if you own a restaurant/hotel and you&#39;d like to know if your customers are satisfied by your services.</li> </ul> <p style="text-align:justify">For these reasons, I decided to show how easy we can train a deep neural network to learn&nbsp;the sentiment behind a text in French. &nbsp;The technical aspects of this document are inspired by this <a href="http://machinelearningmastery.com/sequence-classification-lstm-recurrent-neural-networks-python-keras/">excellent blog</a>. &nbsp; Like there, I am using&nbsp;<a href="https://keras.io/">Keras </a>with a <a href="https://www.tensorflow.org/">Tensorflow </a>backend. The main difference&nbsp;is coming from the&nbsp;training dataset used: &nbsp;they used the usual IMDB dataset, while I use a&nbsp;French dataset of movie reviews that I&nbsp;mined myself (see this&nbsp;<a href="/blog/2016/12/13/scrapping-movie-data-from-static-web/">previous blog post</a>). &nbsp;The idea is to train our model&nbsp;to &quot;read&quot; a&nbsp;(movie review) text and&nbsp;predict&nbsp;what is the mark that is&nbsp;associated with this text (score of&nbsp;the&nbsp;movie). &nbsp;If we have enough pairs of review text - score to train the deep neural network, the algorithm will be capable of understanding which sequence of words have a positive meaning and which ones have a negative meaning. As there is no need to know the vocabulary and/or grammatical rules&nbsp;of the language in this learning&nbsp;process, it can be used for any language in the condition to have a large enough dataset to&nbsp;train the model.</p> <p style="text-align:justify">Before feeding the neural network, we need to perform the traditional NLP pre-processing of the text (I don&#39;t enter into the details as this is very classical approach and it&#39;s already well documented on every single NLP blog):</p> <ol> <li style="text-align:left">Texts are&nbsp;chunked in words based on white space, punctuation, etc. &nbsp; This is done using a Treebank tokenizer of the NLTK&nbsp;library (nltk.tokenize.word_tokenize).</li> <li style="text-align:justify">Characters&nbsp;are turned to lowercase, as I don&#39;t want the algorithm to identify the word polarity&nbsp;based on its capitalization. &nbsp;(Although it could help sometimes).</li> <li style="text-align:justify">A dictionary of all the words used in the dataset is built. &nbsp;The dictionary includes all words variants like verb conjugations. &nbsp;Punctuation marks are&nbsp;also considered as words&nbsp;as I want the algorithm to learn the sentiments behind an entire sentence. Among the top 25 most words used, we have: &#39;,&#39;,&nbsp;de, et,&nbsp;le,&nbsp;la, &nbsp;un,&nbsp;&agrave;,&nbsp;film, les,&nbsp;est,&nbsp;qui,. en,&nbsp;que,&nbsp;une,&nbsp;des,&nbsp;pas,&nbsp;du,&nbsp;ce,&nbsp;dans, !, pour, mais, a, on. &nbsp;Those are mostly French &quot;stopwords&quot; that are present&nbsp;in almost every French text with comparable frequencies. These words do not contain much information. Nonetheless, as I&nbsp;want the algorithm to be language generic I am&nbsp;not removing these words from the dictionary as it&#39;s generally done because identifying stopwords in a foreign language might be challenging. &nbsp;On the other hands, the less used words are often typos, names or very rare words that will add very little to the text analysis. &nbsp;I, therefore, keep only the 10.000 most frequent words in the dataset dictionary and ignore all the other ones.&nbsp; Considering that&nbsp;an&nbsp;average native English speaker knows about 30.000 words&nbsp;and&nbsp;a second-language English speaker knows about 10.000 words, the model is expected to be somehow limited, but not totally ridiculous.</li> <li style="text-align:justify">At this&nbsp;point, the review texts are turned to mathematical vectors. &nbsp;Words of the text are replaced by their index in the dictionary. &nbsp;Words that aren&#39;t present in the dictionary are simply skipped. &nbsp;In order to ease the processing, I&nbsp;fix the size of the vectors to 500 words. &nbsp;Text with more words are trimmed to the first 500 words and&nbsp;text with fewer words are padded with 0. &nbsp;After this, every single review text is represented by a 500 integer vector with components between 0 and 10.000 (the size of the dictionary). &nbsp;This is the input data for the deep neural network that we are going to use.</li> </ol> <p style="text-align:justify"><u><strong>In a first simple model</strong></u>, I used the five following layers for the neural network architecture:</p> <ol> <li style="text-align:justify">An embedding layer which encodes the word integer&nbsp;(comprise between 0 and 10.000) as a float vector of 64 components. &nbsp;I could have used specific embedding algorithm like word2Vec or glove to perform this task, but I prefer to let the network figure out what is the best embedding for this particular dictionary, problem, and objective function. &nbsp;This layer takes as input a 500-vector of integer and returns a 500 x 64 float matrix.</li> <li style="text-align:justify">The following layer is a 1D-convolution layer made of 32 filters each with a length of 3 items. &nbsp;The goal of this layer is to learn the meaning of consecutive word patterns that may have a particular sense. &nbsp;Thinking about sentences like: &quot;I did NOT like this movie&quot;. &nbsp;I expect this layer to catch that &quot;NOT&quot; preceding a verb actually negates the sense of the verb. &nbsp;As I use filters with lengths&nbsp;of maximum 3 words we should be able to catch that type of features at least in English and French &nbsp;(Dutch might be a bit more tricky as the distance between verb and negation can actually be large). &nbsp;This layer takes as input a 500&nbsp;x 64 matrix and returns a 500 x 32&nbsp;filter matrix. &nbsp;A Rectified Linear (ReLU) activation is used for this layer.</li> <li style="text-align:justify">Convolution layers ar traditionally followed by pulling layers which allows keeping the number of parameters in the model under control. &nbsp;In this case, we simply reduce the matrix dimension by a factor of 2 by keeping the strongest &quot;word&quot; in every two &quot;words&quot; window. &nbsp;Word is in a quote as it&#39;s not really a representation of the word anymore after it passed through the convolution layer.&nbsp; &nbsp;This layer, therefore, returns a matrix of dimension 250 x 32 which is twice smaller than it&#39;s input.</li> <li style="text-align:justify">It&#39;s time for the recurrent layer to come in. &nbsp;Here I&nbsp;used an LSTM layer, but I could also have used a GRU layer. &nbsp;I used 256 memory units for this layer in order to catch a maximum number of features within the entire text. &nbsp;A much lower number of smart neurons would certainly have worked as well since the length of the text is at most 500 words. &nbsp;The output of the layer is, therefore, a vector of 256 components.</li> <li style="text-align:justify">Finally, I used a dense layer which would reconnect all the features together and predict a single value between 0 and 1 corresponding to the text polarity. &nbsp;0 would mean a rather negative text and 1 a rather positive text. &nbsp;I used a sigmoid activation for this layer as I want its output to be between 0 and 1.</li> </ol> <p style="text-align:justify"><img alt="rnnsimple" class="alignright size-medium wp-image-758" src="/uploads/uploads/zinnia/2016/12/19//rnnsimple.png?w=572" style="height:300px; width:286px" /></p> <p style="text-align:justify">The table&nbsp;on the right summarizes the model&nbsp;architecture.</p> <p style="text-align:justify">The model is trained on 80.000 reviews (20.000 are used for the model validation) using an ADAM optimizer and a binary cross-entropy objective function. &nbsp;Only reviews with a polarized score &lt;2/5 or &gt;4/5&nbsp;are used for this training and they are marked as 0 or 1 polarity.</p> <p style="text-align:justify">The total number of free parameters in the model is around 1000.000 and the training type is approximately 30 min per epoch on my Intel i5 laptop. &nbsp;After three epoch the accuracy is about 94% (tested on the validation sample).</p> <p><strong>Results examples on other movie reviews:</strong></p> <ul> <li>&quot;Magnifique . Dr&ocirc;le . Touchant . Cruel . Moderne . Humain. Beau . Inventif . Avec un superbe trio d&#39;acteurs .&quot; <span style="color:#0000ff">leads to a sentiment polarity of&nbsp;0.999 (highly positive review).</span></li> <li>&quot;Les personnages sont des paum&eacute;s n&eacute;vros&eacute;s avec leur vision compl&egrave;tement n&eacute;gative de la vie. Ce film se veut original et provocateur &agrave; travers la vulgarit&eacute; et la m&eacute;chancet&eacute; gratuites. A part les premi&egrave;res sc&egrave;nes acceptables, on ne regarde malheureusement la suite qu&#39;en apn&eacute;e. D&eacute;plorable !&quot; <span style="color:#0000ff">&nbsp;leads to a sentiment polarity of&nbsp;0.048 (highly negative&nbsp;review).</span></li> </ul> <p><strong>Results examples on other sentences:</strong></p> <ul> <li>&quot;Je n&#39;ai vraiment pas aim&eacute; ce film. Les acteurs sont mauvais et l&#39;histoire est particuli&egrave;rement nulle.&quot; <span style="color:#0000ff">0.005 (negative sentiment).</span></li> <li>&quot;J&#39;ai vraiment aim&eacute; ce film. Les acteurs sont excellents et l&#39;histoire est originale.&quot; <span style="color:#0000ff">0.998 (positive sentiment)</span></li> <li>&quot;Excellent&quot; <span style="color:#0000ff">0.942 (positive sentiment)</span></li> <li>&quot;Ca ne s&#39;annonce pas bon&quot; <span style="color:#0000ff">0.461&nbsp;(average sentiment)</span></li> <li>&quot;Vraiment &eacute;tonnant&quot; <span style="color:#0000ff">0.854029 (rather positive sentiment)&nbsp;</span></li> <li>&quot;Un peu de gaiet&eacute; et de plaisirs pour ce soir 😀&quot; <span style="color:#0000ff">0.445237 (rather negative sentiment).&nbsp;</span></li> </ul> <p style="text-align:justify">We see that although it&#39;s not always perfect, the algorithm catches most of the time the sentiment behind a sentence/text. &nbsp;Moreover, when it fails the text polarity is close to 0.5 which indicates some confusion regarding the sentiment. &nbsp;But for such scores, it is not possible to know if the algorithm is confused or if the text is neutral.</p> <p>We can actually improve a bit the&nbsp;model using multiple outputs and a softmax in the last layer. &nbsp;In other words, we can train a model that would give us the probability that a text is negative&nbsp;(category 1), neutral (category 2) or&nbsp;positive (category 3). &nbsp;With this type of output, we can also measure the confidence of the algorithm regarding its prediction.</p> <p style="text-align:justify"><strong><img alt="rnncomplex" class="alignright size-medium wp-image-996" src="/uploads/uploads/zinnia/2016/12/19//rnncomplex.png?w=576" style="height:300px; width:288px" /></strong></p> <p style="text-align:justify"><strong>This more advanced model&nbsp;</strong>is completely identical to the previous model (see figure) with the exception&nbsp;of the last layer which outputs a 3-float vector and uses a softmax activation (instead of a sigmoid) in order to guarantee a probability of being in one of the three&nbsp;categories. &nbsp;The objective function used for the training is this time a categorical cross-entropy.</p> <p style="text-align:justify">Of course, this time we also included neutral reviews in the training phase which we write as a (0,1,0) score vector. &nbsp;(1,0,0) and (0,0,1) being used as score vector for the negative and neutral reviews, respectively. &nbsp; &nbsp;All the rest remains unchanged. &nbsp;The number of free parameters in the model is&nbsp;almost unchanged (still around 1.000.000) as the last layer only accounts for a tiny fraction of the model parameters. &nbsp;The training time is slightly larger than&nbsp;for the first model: ~40 min per epoch. &nbsp;But it is still quite reasonable. &nbsp;The model accuracy evaluated on the validation dataset is about 96%. &nbsp;The results for the same sentences as in the previous case are:</p> <p style="text-align:justify">&nbsp;<strong>Results examples of other sentences:</strong></p> <ul> <li>&quot;Je n&#39;ai vraiment pas aim&eacute; ce film. Les acteurs sont mauvais et l&#39;histoire est particuli&egrave;rement nulle.&quot; <span style="color:#0000ff">[ 0.97207683 &nbsp;0.02485518 &nbsp;0.00306799](negative sentiment with 97% probability).</span></li> <li>&quot;J&#39;ai vraiment aim&eacute; ce film. Les acteurs sont excellents et l&#39;histoire est originale.&quot; <span style="color:#0000ff">[ 0.00260297 &nbsp;0.06905617 &nbsp;0.92834091] (positive sentiment with 93% probability)</span></li> <li>&quot;Excellent&quot; <span style="color:#0000ff">[ 0.03898903 &nbsp;0.09006314 &nbsp;0.87094784] (positive sentiment with 87% probability)</span></li> <li>&quot;Ca ne s&#39;annonce pas bon&quot; <span style="color:#0000ff">[ 0.76711851 &nbsp;0.15689771 &nbsp;0.07598375] (negative sentiment with 76% probability)</span></li> <li>&quot;Vraiment &eacute;tonnant&quot; <span style="color:#0000ff">[0.05729499 &nbsp;0.11347752 &nbsp;0.82922751] (positive sentiment with 83% probability)&nbsp;</span></li> <li>&quot;Un peu de gaiet&eacute; et de plaisirs pour ce soir 😀&quot; <span style="color:#0000ff">[ 0.40064782 &nbsp;0.21832566 &nbsp;0.38102651] (mitigated&nbsp;sentiment).&nbsp;</span></li> </ul> <p style="text-align:justify">We can see that the last sentence is clearly confusing the algorithm which isn&#39;t capable of identifying a clear sentiment behind this sentence. &nbsp;But this time, we can spot that all the algorithm predictions are under a 50% probability which is therefore rather weak.</p> <p style="text-align:justify">The model can, of course, be further customized and improved, but I am stopping here for this tutorial. &nbsp;For the most curious one, here is the code I used to define and train the advanced model. &nbsp;As you can see, there is nothing really striking there.</p> <pre> <code class="language-python">from keras.datasets import imdb from keras.models import Sequential from keras.layers import Dense from keras.layers import LSTM from keras.layers.convolutional import Convolution1D from keras.layers.convolutional import MaxPooling1D from keras.layers.embeddings import Embedding from keras.preprocessing import sequence ... # create the model embedding_vecor_length = 64 model = Sequential() model.add(Embedding(top_words, embedding_vecor_length, input_length=max_review_length)) model.add(Convolution1D(nb_filter=32, filter_length=3, border_mode='same', activation='relu')) model.add(MaxPooling1D(pool_length=2)) model.add(LSTM(256, dropout_W=0.2, dropout_U=0.2)) model.add(Dense(3, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) print(model.summary()) model.fit(Sequences[:split], Labels[:split], nb_epoch=3, batch_size=64) # Final evaluation of the model scores = model.evaluate(Sequences[split:], Labels[split:], verbose=0) print("Accuracy: %.2f%%" % (scores[1]*100))</code></pre> <p><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> loic.quertenmont@gmail.com (Loic Quertenmont)Mon, 19 Dec 2016 07:05:20 +0000http://deeperanalytics.be/blog/2016/12/19/sentiment-analysis-of-french-texts-using-deep-learning-techniques/Business CaseDeep learningNatural Language ProcessingScrapping social data from Facebook http://deeperanalytics.be/blog/2016/12/15/scrapping-social-data-from-facebook/ <p style="text-align:justify">Nowadays, social networks&nbsp;can&nbsp;be considered as a main source of data. &nbsp;This is particularly true for business to customer companies which must&nbsp;take into account customer feedback on their products. &nbsp;In this blog, we will show how to retrieve information from&nbsp;Facebook using the Facebook Graph API...<!--more--></p> <p style="text-align:justify">As a showcase, we will retrieve the activity over the last 6 months on the Facebook page of the Belgian Railway company (SNCB / NMBS). &nbsp;To do so, we will use the <a href="https://facebook-sdk.readthedocs.io/en/latest/">Facebook SDK package for python</a>&nbsp;which provides handy functions to interact with the Graph API.</p> <p>The first thing that we need is an identification token to connect to the <a href="https://developers.facebook.com/docs/graph-api">Graph API</a>. &nbsp;The Facebook token allows us to give specific permission to a given application. &nbsp;For instance, we can allow it to see our list of friends, our email address etc. &nbsp;Facebook is actually quite strict regarding the protection of user data. &nbsp; For what we will do today, we don&#39;t need any of these permissions as we are not trying to get information from a particular user but instead we want to access a public Facebook page and extract from it as much public information as possible. &nbsp;The token can be&nbsp;obtained via OAuth2 identification but in order to keep this blog simple, we will request a token using the <a href="https://developers.facebook.com/tools/explorer/">Facebook Explorer interface</a>.&nbsp;On this page, you need to&nbsp;press the &quot;get token&quot; button, and chose &quot;get user token&quot;. &nbsp;A popup window&nbsp;opens where you can choose which permissions you want to associate to this token. &nbsp;We don&#39;t need any permission for what we plan to do, so no need to check any of the boxes. Be aware that the user token has a limited time validity of about 2 hours. &nbsp;So you may have to request a new token from time to time.</p> <p style="text-align:justify">Now that we have a token, we can make a query on the Facebook API using the python SDK. &nbsp;The code bellow shows how to parse all posts on the Facebook page of the SNCB &nbsp;(Belgian railway). &nbsp; This is done via the &quot;getConnections&quot; function which takes as argument a Facebook object&nbsp;Id &nbsp;(here the Id of the SNCB page on Facebook) and the type of contents we want to grab (here the posts on the page). &nbsp;As the number of post on a page can be a pretty large number, Facebook graph API only returns the first posts. &nbsp;But, it also returns a pointer (via a &quot;paging&quot; object) to get the following bunch of posts, so if we want to process all posts of a page we will need to process them bunch by bunch until there is no following bunch available. &nbsp;See the <a href="https://developers.facebook.com/docs/graph-api">graph API documentation</a> for more details.</p> <pre> <code class="language-python">import facebook #set token we received from Facebook explorer (the one bellow is out dated) access_token = 'EAACEdEose0cBAPWTcaMppNFceRnsORWCFSiaaQD8Gr7UZArgl7xZBuucoUz96g3QmmP2tZCgR2DAlLl4sxmnmlabArULdZBGsqM7KcUHzlLZBJvRH6FWnVBfeYt7bAW5fZAWZCZALQnE0BhRQxraAUKW7ec0H6cwzL7GwkKGXZA435gZDZD' public_page = 'sncb' graph = facebook.GraphAPI(access_token) pageFB = getObject(public_page) posts = getConnections(pageFB['id'], connection_name='posts', summary='true') while(True): for p in posts['data']: #we can process a facebook post (for this example, we just print it) print(p) if( ('paging' not in posts) or ('next' not in posts['paging'])): #we have processed all posts, exit the while loop break else: #there are more post to grab, so get a new bunch of post posts = requests.get(posts['paging']['next']).json()</code></pre> <p style="text-align:justify">As you can see, the amount&nbsp;of information contained in one single post is quite impressive. &nbsp;You can note that the information is saved in the form of a python dictionary which is particularly convenient to access specific fields. &nbsp;See what I get for the first post:</p> <pre> <code class="language-python">story_tags : {'0': [{'type': 'page', 'id': '484217188294962', 'name': 'SNCB', 'offset': 0, 'length': 4}]} from : {'id': '484217188294962', 'name': 'SNCB', 'category': 'Transportation Service', 'category_list': [{'id': '152367304818850', 'name': 'Transportation Service'}, {'id': '2258', 'name': 'Travel Company'}]} link : https://www.facebook.com/SNCB/photos/a.844798168903527.1073741830.484217188294962/1157476480969026/?type=3 is_expired : False updated_time : 2016-12-15T07:53:27+0000 actions : [{'name': 'Comment', 'link': 'https://www.facebook.com/484217188294962/posts/1157476480969026'}, {'name': 'Like', 'link': 'https://www.facebook.com/484217188294962/posts/1157476480969026'}] icon : https://www.facebook.com/images/icons/photo.gif is_hidden : False message : "Les accompagnateurs de train Anneleen et Bart sont fiancés ! &lt;3\nLeurs regards se sont croisés pour la première fois dans le centre de formation où leur carrière a débuté en 2011. Ce n’est qu’un an plus tard qu’a jailli l’étincelle, lors de leur premier rendez-vous. La gare de Louvain a récemment été le théâtre de la demande en mariage.\nNous leur souhaitons beaucoup de bonheur ensemble !" object_id : 1157476480969026 shares : {'count': 75} likes : {'paging': ... TRUNCATED ...} privacy : {'friends': '', 'deny': '', 'allow': '', 'description': '', 'value': ''} created_time : 2016-12-14T10:40:21+0000 type : photo name : Timeline Photos id : 484217188294962_1157476480969026 status_type : added_photos story : SNCB feeling in love. picture : https://scontent.xx.fbcdn.net/v/t1.0-0/p130x130/15492137_1157476480969026_4126379523377925813_n.jpg?oh=a99414cf126f04e12de4a006a435fc56&amp;oe=58BA1CD4 comments : {'paging': .... TRUNCATED ... }</code></pre> <p style="text-align:justify">Among other things, we have the post message, creation time, the name of the author, type of post, a story description of the post, a link to the post picture and pointers to all the likes and comments (including who liked/commented on this post). &nbsp;That&#39;s a gigantic source of information for analytics. &nbsp;With this, we can identify who likes on this page (and is, therefore, concerned about Belgian Railway company), we can analyze the comments made on a post and possibly trigger unsatisfied customer message (and take action to improve the situation), we can do user segmentation based on the user profiles,&nbsp;etc.</p> <p>As a simple benchmark for this post, we&nbsp;will analyze the ~150 posts that were published on the SNCB page of the last 6 months. &nbsp;We can look first at the post popularity in terms of likes, shares, and comments:</p> <p style="text-align:center"><img alt="likestrend" class="alignnone size-full wp-image-591" src="/uploads/uploads/zinnia/2016/12/15//likestrend.png" style="width:100%" /></p> <p style="text-align:center">&nbsp;</p> <p style="text-align:justify">For the 15 most popular posts we show the post name. &nbsp;We can notice that for most of them, the post name is &quot;Timeline Photos&quot; which is a name generated by Facebook&nbsp;when a new picture is uploaded. &nbsp;We can therefore already notice that &quot;picture&quot; post are more popular than text or news post. &nbsp;This is precious information for the communication strategy of the company. &nbsp;We can continue by analyzing the text of the post in order to identify the topic of interest on SNCB page. &nbsp;To do so I used the&nbsp;simple&nbsp;entity extraction of the <a href="http://www.nltk.org/">NLTK python library</a>&nbsp;and plot them&nbsp;as a <a href="https://github.com/amueller/word_cloud">word cloud</a> &nbsp;(word size depends on frequency):</p> <p style="text-align:center"><img alt="wordCloud.png" class="alignnone size-full wp-image-606" src="/uploads/uploads/zinnia/2016/12/15//wordcloud.png" style="width:100%" /></p> <p style="text-align:center">&nbsp;</p> <p style="text-align:justify">As expected, Mobility, railway stations, and special exhibitions are the main center of interest. &nbsp;We don&#39;t learn anything very striking here, but this is just an example. &nbsp;What is more interesting to do is to analyze the text of the user reactions to SNCB posts (but we will not do it here). &nbsp;We can continue&nbsp;our simple page analytics by finding who are the major post likers for this page. &nbsp;As most of the Facebook users are actually using their real name as Facebook pseudo, this analysis is particularly interesting as it allows you to identify (most of the time) real people who are the defender of your products/brand. &nbsp;And eventually to identify the ones who have issues with your products. &nbsp;That&#39;s again very useful information&nbsp;as it gives you a chance to engage communication with them and solve the problem they may have and eventually prevent&nbsp;churn. &nbsp;Bellow&nbsp;are two figures showing the&nbsp;best fan of the SNCB page in terms of a #likes/user distribution and on the form of a word cloud.</p> <p style="text-align:center"><img alt="fanLikes.png" class="alignnone size-full wp-image-626" src="/uploads/uploads/zinnia/2016/12/15//fanlikes.png" style="width:100%" /></p> <p style="text-align:center">&nbsp;</p> <p style="text-align:center"><img alt="fanCloud.png" class="alignnone size-full wp-image-625" src="/uploads/uploads/zinnia/2016/12/15//fancloud.png" style="width:100%" /></p> <p style="text-align:center">&nbsp;</p> <p style="text-align:justify">We can perform many more&nbsp;analyses based on Facebook data, but I will stop here for this blog. &nbsp;Another one with more complex (and interesting) analytics will be released soon...</p> <p style="text-align:justify"><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> loic.quertenmont@gmail.com (Loic Quertenmont)Thu, 15 Dec 2016 07:05:19 +0000http://deeperanalytics.be/blog/2016/12/15/scrapping-social-data-from-facebook/Data miningScrapping land invest data from dynamic web http://deeperanalytics.be/blog/2016/12/14/scrapping-land-invest-data-from-dynamic-web/ <p style="text-align:justify">In a <a href="/blog/2016/12/13/scrapping-movie-data-from-static-web/">previous blog post</a>, we have seen how to mine information on static web pages. &nbsp;In this blog post, I&#39;ll explain how we can do the same on dynamically (i.e. javascript) generated web pages. &nbsp;As a showcase, I will show you how to find the best land investment you can make in Belgium today... <!--more--> We all know many websites of classified advertising for houses and lands selling. &nbsp;Those sites contain a large amount of interesting information for what concerns land invest. &nbsp;Unfortunately, most of the time, those websites are&nbsp;heavily using javascript to dynamically generate the web page based on the client information (i.e. language, type of web browser, screen size, geographical position, cached data, etc.). &nbsp;If we try to mine these sites using the techniques detailed in the previous blog, the only thing that we will get is actually a small part of the javascript code&nbsp;that is executed when the page is opened in a real browser. &nbsp;This makes the data harvesting particularly complicated.</p> <p>Fortunately, they are useful python libraries that can be used to solve this problem. &nbsp;My favorite is <a href="http://selenium-python.readthedocs.io/">Selenium</a>&nbsp;which could be used to open a real web browser (i.e. Firefox or chrome) and automatize the used behavior like clicking on a link, filling a form, pressing a button, etc. &nbsp;Many selenium tutorials can be found on <a href="https://www.guru99.com/selenium-tutorial.html">Guru99</a>. &nbsp;The only thing that we have to do is to inspect the page (as explained in the previous blog) in order to identify the name of the elements of interest on the web page and tell selenium the sequence of action we want him to do on a page. &nbsp;Actually, our work is even more simplified by additional web browser plugins like <a href="https://addons.mozilla.org/fr/firefox/addon/selenium-ide/">Selenium IDE</a>&nbsp;which could be integrated into your Firefox browser to record (and later export) all the actions you make on a web page. &nbsp;This allows to very quickly automatize repetitive behavior on the web. &nbsp;</p> <p style="text-align:justify">Below is a small example of the selenium capabilities. &nbsp;In this demo, we simply open a Firefox web browser on the google page and search for the term &quot;selenium&quot;.</p> <pre> <code class="language-python">import os,time from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.keys import Keys #initialize the web browser os.environ["PATH"] += ":/PathTo/geckodriver" #needed for recent firefox versions firefox_capabilities = DesiredCapabilities.FIREFOX firefox_capabilities['marionette'] = True driver = webdriver.Firefox(capabilities=firefox_capabilities) driver.implicitly_wait(10) # timeout for page to load #go to google.com driver.get("http://www.google.com") #find the searchbox and put "selenium" text in it driver.find_element_by_id("lst-ib").send_keys("selenium") #wait a little bit for the text to be sent time.sleep(1) #press the search button (this will move us to the results page) driver.find_element_by_id("lst-ib").send_keys(Keys.RETURN) #convert the results page to a beautifulSoup object and print it soup = BeautifulSoup(driver.page_source, "lxml") print(soup.body.get_text(" ")) #wait 1min before closing the window (this is just for the demo) time.sleep(60) driver.quit()</code></pre> <p style="text-align:justify">So, this is it for the technical aspect of this blog post, we can now move to the logic of today&#39;s showcase. &nbsp;Again, I am not going to give specific code I used in order to preserve the server of the&nbsp;classified advertising website I&#39;ve used. &nbsp;However, I can speak about the logic of the algorithm and about the results I&#39;ve got. &nbsp;For collecting the data about the land selling market in Belgium, here are the typical actions we want to perform:</p> <ol> <li>Search for land for building opportunities in a Belgian city (based on a zip&nbsp;code) <ol> <li>Find the search&nbsp;form on the page</li> <li>Select &quot;land for building&quot; as type of good</li> <li>Fill the zip code in the search box</li> <li>Press the search button</li> </ol> </li> <li>Wait for the results page to load</li> <li>Parse the result pages <ol> <li>Convert the page to&nbsp;a BeautifulSoup object (as in the previous blog post) and iterate on all the element of interest we want to gather</li> <li>Search for a &quot;next page&quot; link and click the link if it exist</li> <li>Go&nbsp;to step 3.1. and iterate until all results page even been downloaded</li> </ol> </li> <li>Go to step 1. and iterate with another zip code</li> </ol> <p style="text-align:justify">As you can see the logic is rather simple and thanks to Selenium IDE, all these actions can be recorded in a couple of minutes. &nbsp;Then the only thing to do is to integrate all this in a python loop on city zip codes we want to analyze. &nbsp;I processed all post in all Belgian cities during the month of October and collected for each&nbsp;the description of the good, the surface of the land, the selling&nbsp;price, the zip code and the town. &nbsp;It took less than two hours to collect all the data, corresponding to approximately 10.000 posts.&nbsp;You can find bellow some figures&nbsp;made out of these data.</p> <p><img alt="count" class="alignnone size-full wp-image-1974" src="/uploads/uploads/zinnia/2016/12/14//count1.png" style="width:100%" /></p> <p style="text-align:center">The number of posts collected per Belgian city zone (white indicate no post found).</p> <p><img alt="surf" class="alignnone size-full wp-image-1977" src="/uploads/uploads/zinnia/2016/12/14//surf1.png" style="width:100%" /></p> <p style="text-align:center">Average surface&nbsp;(in m&sup2;) of the lands for buildings being sold in each city zone.</p> <p><img alt="price" class="alignnone size-full wp-image-1975" src="/uploads/uploads/zinnia/2016/12/14//price1.png" style="width:100%" /></p> <p style="text-align:center">Average price (in euro) of the lands for buildings being sold in each city zone.</p> <p><img alt="priceNorm" class="alignnone size-full wp-image-1976" src="/uploads/uploads/zinnia/2016/12/14//pricenorm1.png" style="width:100%" /></p> <p style="text-align:justify">The average price per surface (&euro;/m&sup2;) of the lands for buildings being sold in each city zone.</p> <p style="text-align:justify">The last figure can be compared to the <a href="http://statbel.fgov.be/fr/statistiques/chiffres/economie/construction_industrie/immo/prix_moyen_terrains/">official figure</a> made by the Belgian government in 2014. &nbsp;In can be seen that despite we used data from only one data harvesting in October 2016, the two figures are very similar and we can reproduce all the trends that are observed on the official figure: &nbsp;high price in Brussels and on the Belgian coast. &nbsp;The scale of the price is also quite comparable. We can also notice that the size of the lands being sold is much larger in Wallonia compare to Flanders, but the price/m&sup2; is also much lower.</p> <p style="text-align:justify">Now that we have meaningful data, we can start looking for the best investment. &nbsp;To do so, we will look for the land which as a price/m&sup2; significantly lower than the average for its town. &nbsp;In order to accommodate with the limited statistics we have, we will only consider towns for which we have at least 5 offers (in order to have a reasonable error on the&nbsp;average). &nbsp;Bellow is the list of the 25 best investments you can make according to the average of the price in the town. &nbsp;In the top 10, nine goods&nbsp;are located in Flanders which is certainly meaning something...</p> <pre> <code>Land of 1614 m² to sell at 215000 € (133.21 €/m²) in 1860 meise (average for the town is 352.58+- 66.56 €/m²) Land of 1445 m² to sell at 125000 € ( 86.51 €/m²) in 3950 bocholt (average for the town is 152.01+- 20.83 €/m²) Land of 15950 m² to sell at 595000 € ( 37.30 €/m²) in 2550 kontich (average for the town is 524.20+-155.83 €/m²) Land of 3326 m² to sell at 144000 € ( 43.30 €/m²) in 3320 hoegaarden (average for the town is 232.71+- 61.52 €/m²) Land of 2013 m² to sell at 107000 € ( 53.15 €/m²) in 3560 lummen (average for the town is 173.30+- 39.51 €/m²) Land of 2487 m² to sell at 175000 € ( 70.37 €/m²) in 3520 zonhoven (average for the town is 179.41+- 36.29 €/m²) Land of 1810 m² to sell at 135000 € ( 74.59 €/m²) in 3990 peer (average for the town is 177.42+- 34.83 €/m²) Land of 2680 m² to sell at 90000 € ( 33.58 €/m²) in 3970 bourg-leopold (average for the town is 167.06+- 46.53 €/m²) Land of 1386 m² to sell at 225000 € (162.34 €/m²) in 1860 meise (average for the town is 352.58+- 66.56 €/m²) Land of 3050 m² to sell at 57000 € ( 18.69 €/m²) in 5350 ohey (average for the town is 45.65+- 9.70 €/m²) Land of 10491 m² to sell at 32000 € ( 3.05 €/m²) in 6640 vaux-sur-sure (average for the town is 42.92+- 14.34 €/m²) Land of 14201 m² to sell at 90000 € ( 6.34 €/m²) in 6860 leglise (average for the town is 56.48+- 18.47 €/m²) Land of 7000 m² to sell at 165000 € ( 23.57 €/m²) in 1370 jodoigne-souveraine (average for the town is 85.28+- 23.38 €/m²) Land of 2156 m² to sell at 312000 € (144.71 €/m²) in 2870 breendonk (average for the town is 269.68+- 47.35 €/m²) Land of 1521 m² to sell at 128200 € ( 84.29 €/m²) in 3670 meeuwen-gruitrode (average for the town is 228.11+- 54.78 €/m²) Land of 6858 m² to sell at 399000 € ( 58.18 €/m²) in 2310 rijkevorsel (average for the town is 250.75+- 74.73 €/m²) Land of 6000 m² to sell at 280000 € ( 46.67 €/m²) in 1570 gammerages (average for the town is 221.27+- 68.20 €/m²) Land of 1858 m² to sell at 275000 € (148.01 €/m²) in 1780 wemmel (average for the town is 423.63+-110.28 €/m²) Land of 12370 m² to sell at 149000 € ( 12.05 €/m²) in 6470 sivry-rance (average for the town is 37.70+- 10.32 €/m²) Land of 1371 m² to sell at 125000 € ( 91.17 €/m²) in 3990 peer (average for the town is 177.42+- 34.83 €/m²) Land of 12000 m² to sell at 100000 € ( 8.33 €/m²) in 5377 somme-leuze (average for the town is 46.75+- 15.63 €/m²) Land of 13860 m² to sell at 35000 € ( 2.53 €/m²) in 4190 ferrieres (average for the town is 45.60+- 17.78 €/m²) Land of 770 m² to sell at 30000 € ( 38.96 €/m²) in 2235 hulshout (average for the town is 227.01+- 78.15 €/m²) Land of 7700 m² to sell at 84000 € ( 10.91 €/m²) in 6670 gouvy (average for the town is 39.97+- 12.15 €/m²)</code></pre> <p><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> loic.quertenmont@gmail.com (Loic Quertenmont)Wed, 14 Dec 2016 07:05:18 +0000http://deeperanalytics.be/blog/2016/12/14/scrapping-land-invest-data-from-dynamic-web/Business CaseData miningScrapping movie data from static web http://deeperanalytics.be/blog/2016/12/13/scrapping-movie-data-from-static-web/ <p style="text-align:justify">Every data science journey&nbsp;starts by aggregating the data of interest. &nbsp;In the industry sector, those are often coming directly from sensors, user surveys, software or application&nbsp;used by your customers. &nbsp;Nonetheless, the information publicly available on the web still remain an important source of additional information&nbsp;like news, weather&nbsp;or even geographical addresses. &nbsp;Today, we will focus on&nbsp;movie data...<!--more--></p> <p style="text-align:justify">In this post, I will present some techniques to scrap static web pages in python 3. &nbsp;In this context, static mean pages for which the information is not dynamically generated by the web browser&nbsp; (using i.e. javascript functions)&nbsp;but are instead generated on the server-side and don&#39;t require your browser to dynamically generate information. &nbsp;Dynamic website scrapping will be addressed in a different post.</p> <p style="text-align:justify">For this showcase, we will scrap a well-known french website of movie reviews. For all accessible movies, we will collect the movie director, the main actors, the movie title, and synopsis and some additional information like the release date, the duration of the movie, the type of movie (drama, comedy...). &nbsp;In addition, we will also collect&nbsp;user reviews (score and critics) for each&nbsp;movie. &nbsp;The name of the movie review website that is used is not mentioned on purpose in order to avoid massive load on their servers. &nbsp;Nevertheless, the techniques described here can be used on your favorite website.</p> <p style="text-align:justify">In python 3, the easiest way to access the content of a web page is via the urllib.request package. &nbsp;See bellow a typical example to scrap the google page. &nbsp;Note that we provide the &quot;User-Agent&quot; Http header in the Request constructor because many websites only allow access to their content to well-known web browser ( Google Chrome in this case).</p> <pre> <code class="language-python">from urllib.request import urlopen, Request url = "http://www.google.com" try: req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'}) pageHtml = urllib.request.urlopen(req, timeout=5) print(pageHtml.read()) #print the text of the page pageHtml.close() except Exception as e: #handle for timeout, wrong URL, wrong permission, etc. print("Exception with url " + str(url)+" at line " + str(sys.exc_info()[-1].tb_lineno) + "\n" +str(e))</code></pre> <p style="text-align:justify">If you try to run the above code, you will see that what we receive is nothing but the Html code of the google page. &nbsp;That&#39;s all we need as the information we are looking for is hidden somewhere inside there. &nbsp;Many packages are available in python to help us navigate within the Html code and help us to collect the information we need. &nbsp;The most convenient (and famous) to use is <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">BeautifulSoup</a>. &nbsp;We can build a BeautifulSoup object from our web page using the following code:</p> <pre> <code class="language-python">from bs4 import BeautifulSoup page = BeautifulSoup( pageHtml , "lxml") print(page.body.get_text(" ", strip=True)) #print all the text found on the webpage</code></pre> <p style="text-align:justify">What is particularly interesting with BeautifulSoup is that we can easily search for all Html object on the page with a given class name. &nbsp;In modern web design, class name is very often used to categorize the different object displayed on a page. &nbsp;In the case that interest us, movie review website are often&nbsp;constructed as a table of movie where each row contains the information we are interested in. &nbsp;If we continue with the google example, we can easily retrieve the search text entry by inspecting the web page in a web browser &nbsp;(I used chrome). &nbsp;This is made easy by right clicking on the element for which we can to retrieve the class name and selecting &quot;inspect&quot; in the contextual menu.&nbsp; The code of the element is highlighted on the right inspection tab.&nbsp; The class name is generally one of the first entry of the block.</p> <p><img alt="inspectpage" class="aligncenter size-full wp-image-161" src="/uploads/uploads/zinnia/2016/12/13//inspectpage.png" style="height:480px; width:987px" /> Retrieving the class name of an object on a page is very simple thanks to the &quot;inspect&quot; option of modern web browsers.[/caption]</p> <p style="text-align:justify">We can then access this element from our code by searching for all elements which have a class name&nbsp;&quot;gsfi&quot; using BeautifulSoup find_all method. &nbsp;It returns a list of found object.</p> <pre> <code class="language-python">searchEntries = page.body.find_all(class_="gsfi", recursive=True) if(len(searchEntries)==0): print("Object was not found on page")</code></pre> <p style="text-align:justify">On more complex web page (i.e. movie review sites) we can then loop on all the object found in order to extract the information we are looking for using the get_text method.</p> <pre> <code class="language-python">for entry in searchEntries: print("Found an entry with text" + entry.get_text(" ", strip=True))</code></pre> <p style="text-align:justify">We now have all the ingredients to scrap any type of static web page. &nbsp;So it&#39;s probably a good time to remind that they are some good practice to follow when we scrap a website. Many information and details are available on <a href="https://www.scrapehero.com/how-to-prevent-getting-blacklisted-while-scraping/">scraphero</a>, but for me, the more important advices are:</p> <ul> <li>Don&#39;t go too fast: &nbsp;If you try to load too many pages at the same time, you have good chances to saturate the server and to be banned forever from the website.</li> <li>Follow the rules&nbsp;provided by the robot.txt file of the website. &nbsp;This file indicates what is allowed in terms of scrapping on their website.</li> </ul> <p style="text-align:justify">We can now proceed and collect our movie data...</p> <hr /> <p style="text-align:justify">As explained earlier, I am not going to give specific code in order to preserve the movie website server. &nbsp;However, I can show some&nbsp;of the information collected and some&nbsp;of the fun we can have with them. &nbsp;Bellows are three example of extracted movies out of the 70000 movies (~1GB of data) I collected.</p> <p style="text-align:justify"><strong><u>Elysium:</u></strong> <strong>Synopsis</strong>: En 2154, il existe deux cat&eacute;gories de personnes&nbsp;: ceux tr&egrave;s riches, qui vivent sur la parfaite station spatiale cr&eacute;e par les hommes appel&eacute;e Elysium, et les autres, ceux qui vivent sur la Terre devenue surpeupl&eacute;e et ruin&eacute;e. <strong>Director:</strong> Neill Blomkamp <strong>Actors:</strong> Matt Damon , Jodie Foster , Sharlto Copley <strong>Extra data:</strong> 14 ao&ucirc;t 2013 / 1h 50min / Science fiction , Action , Thriller</p> <p style="text-align:justify"><u><strong>Le P&egrave;re No&euml;l est une ordure:</strong></u> <strong>Synopsis:</strong> La permanence t&eacute;l&eacute;phonique parisienne SOS d&eacute;tresse-amiti&eacute; est perturb&eacute;e le soir de No&euml;l par l&#39;arriv&eacute;e de personnages marginaux farfelus qui provoquent des catastrophes en cha&icirc;ne. <strong>Director:</strong> Jean-Marie Poir&eacute; <strong>Actors:</strong> An&eacute;mone , Josiane Balasko , Marie-Anne Chazel <strong>Extra data:</strong> 25 ao&ucirc;t 1982 / 1h 23min / Com&eacute;die</p> <p style="text-align:justify"><u><strong>Expendables 3:</strong></u> <strong>Synopsis:</strong> Barney, Christmas et le reste de l&rsquo;&eacute;quipe affrontent Conrad Stonebanks, qui fut autrefois le fondateur des Expendables avec Barney. Stonebanks devint par la suite un redoutable trafiquant d&rsquo;armes, que Barney fut oblig&eacute; d&rsquo;abattre&hellip; Du moins, c&rsquo;est ce qu&rsquo;il croyait. <strong>Director:</strong> Patrick Hughes (II) <strong>Actors:</strong> Sylvester Stallone , Jason Statham , Arnold Schwarzenegger <strong>Extra data:</strong> 20 ao&ucirc;t 2014 / 2h 07min / Action</p> <p style="text-align:justify">As you can see the movie details are quite complete. &nbsp;I have not listed there the&nbsp;user reviews associated with these movies are they are many reviews for each move and that those are often very lengthy. &nbsp;Nonetheless, we have the data and can use them if needed.</p> <hr /> <p style="text-align:justify">We can now perform some analysis on these data in order to find insights,</p> <p><strong>Who are&nbsp;the directors who have made more&nbsp;movie? </strong></p> <ol> <li>John Ford : 84 movies</li> <li>Raoul Ruiz : 66 movies</li> <li>Kenji Mizoguchi : 63 movies</li> <li>Henry Hathaway : 57 movies</li> <li>Jes&uacute;s Franco : 56 movies</li> <li>Julien Duvivier : 54 movies</li> <li>Claude Chabrol : 53 movies</li> <li>Jean-Pierre Mocky : 52 movies</li> <li>Seijun Suzuki : 52 movies</li> <li>Raoul Walsh : 51 movies</li> </ol> <p>&nbsp; <strong>Who are&nbsp;the actors who have played in&nbsp;more movie ? </strong></p> <ol> <li>G&eacute;rard Depardieu : 116 movies</li> <li>Michel Piccoli : 107 movies</li> <li>Catherine Deneuve : 102 movies</li> <li>Robert De Niro : 90 movies</li> <li>Bernard Blier : 89 movies</li> <li>Jean Gabin : 88 movies</li> <li>Michel Serrault : 86 movies</li> <li>Fernandel : 84 movies</li> <li>Jean-Louis Trintignant : 83 movies</li> <li>Jeanne Moreau : 82 movies</li> </ol> <p style="text-align:justify">Of course, we can see that french actors and directors appear in this top 10. &nbsp;This might be a bias induced by the &quot;french&quot; origin of the movie review site that we have scrapped. &nbsp;There is also a &quot;temporal&quot; bias as we considered all movies without time window. &nbsp;Making similar top 10&nbsp;considering only movies produced in the last decade would lead to very different results.</p> <p><strong>What are all the movies where Tom Hanks played ?</strong> (in random order)</p> <ul> <li>The Circle (de James Ponsoldt)</li> <li>Les Monstres du labyrinthe (de&nbsp;Steven Hilliard Stern)</li> <li>Extr&ecirc;mement fort et incroyablement pr&egrave;s (de Stephen Daldry)</li> <li>Mister Showman (de Sean McGinly)</li> <li>Greyhound (de Aaron Schneider)</li> <li>Dans l&#39;ombre de Mary - La promesse de Walt Disney (de John Lee Hancock)</li> <li>Il n&#39;est jamais trop tard (de Tom Hanks)</li> <li>Turner &amp; Hooch (de Roger Spottiswoode)</li> <li>Les Sentiers de la perdition (de Sam Mendes)</li> <li>Le Palace en folie (de Neal Israel)</li> <li>Misery Loves Comedy (de Kevin Pollak)</li> <li>Nuits blanches &agrave; Seattle (de Nora Ephron)</li> <li>Vous avez un message (de Nora Ephron)</li> <li>Capitaine Phillips (de Paul Greengrass)</li> <li>Every time we say goodbye (de Moshe Mizrahi)</li> <li>Les Banlieusards (de Joe Dante)</li> <li>Philadelphia (de Jonathan Demme)</li> <li>Toujours pr&ecirc;ts (de Nicholas Meyer)</li> <li>Cloud Atlas (de Lilly Wachowski)</li> <li>La Guerre (de Lynn Novick)</li> <li>Sully (de Clint Eastwood)</li> <li>Joe contre le volcan (de John Patrick Shanley)</li> <li>Une &Eacute;quipe hors du commun (de Penny Marshall)</li> <li>Big (de Penny Marshall)</li> <li>La Guerre selon Charlie Wilson (de Mike Nichols)</li> <li>Le Mot de la fin (de David Seltzer)</li> <li>Rien en commun (de Garry Marshall)</li> <li>And the Oscar goes to (de Jeffrey Friedman)</li> <li>Splash (de Ron Howard)</li> <li>Inferno (de Ron Howard)</li> <li>Anges et d&eacute;mons (de Ron Howard)</li> <li>Da Vinci Code (de Ron Howard)</li> <li>Apollo 13 (de Ron Howard)</li> <li>Dragnet (de Tom Mankiewicz)</li> <li>Le B&ucirc;cher des vanit&eacute;s (de Brian De Palma)</li> <li>Ithaca (de Meg Ryan)</li> <li>Il faut sauver le soldat Ryan (de Steven Spielberg)</li> <li>Arr&ecirc;te-moi si tu peux (de Steven Spielberg)</li> <li>Le Pont des Espions (de Steven Spielberg)</li> <li>Le Terminal (de Steven Spielberg)</li> <li>L&#39;Homme &agrave; la chaussure rouge (de Stan Dragoti)</li> <li>Le P&ocirc;le Express (de Robert Zemeckis)</li> <li>Seul au monde (de Robert Zemeckis)</li> <li>Forrest Gump (de Robert Zemeckis)</li> <li>La Ligne verte (de Frank Darabont)</li> <li>A Hologram for the King (de Tom Tykwer)</li> <li>Une Baraque &agrave; tout casser (de Richard Benjamin)</li> <li>Ladykillers (de Ethan Coen)</li> </ul> <p><strong>Who are the favorite actors of&nbsp;Steven Spielberg, Ron Howard and Robert Zemeckis&nbsp;&nbsp;? and how many films do they have in common?</strong></p> <p style="text-align:justify">The figure bellow shows a graph network of the actor shared between these three directors. For clarity of the figures, only the actors that are in the top 500 actors are shown. Green dots symbolizes actors, red dots symbolizes directors and each line correspond to a movie which connects a&nbsp;director and an actor. &nbsp;When more than one line connects a director to an actor, it means that there <span style="background-color:rgba(250,0,30,0.14902)">are</span>&nbsp;several movies connecting them.</p> <p><img alt="two-nodes" class="alignnone size-full wp-image-297" src="/uploads/uploads/zinnia/2016/12/13//two-nodes.png" style="width:60%" /></p> <p><strong>Have you already faced similar type of issues ?&nbsp; </strong><strong><strong>Feel free to contact us, we&#39;d love talking to you&hellip;</strong></strong></p> <p style="text-align:right"><em>If you&nbsp;enjoyed reading this post, please like it. It doesn&#39;t cost you anything, but matters for me!</em></p> loic.quertenmont@gmail.com (Loic Quertenmont)Tue, 13 Dec 2016 07:05:18 +0000http://deeperanalytics.be/blog/2016/12/13/scrapping-movie-data-from-static-web/Data mining Index already exists