tag:linuxfr.org,2005:/users/jnanar--2LinuxFr.org : les contenus de jnanar2020-12-28T17:34:23+01:00/favicon.pngtag:linuxfr.org,2005:Diary/395242020-12-26T12:58:05+01:002020-12-26T12:58:05+01:00Un ami a la carteLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Cher journal,</p>
<p>Ça fait longtemps que je ne t'ai plus écrit. Je vais réparer cette absence aujourd'hui. <br>
L'année a été compliquée pour tout le monde et je suis content de pouvoir envisager celle qui suit de manière plus sereine. Je te <a href="//linuxfr.org/users/jnanar--2/journaux/je-me-fais-des-amis-au-sens-litteral">l'ai déjà dit</a>, mais je ne serai plus jamais seul. Cette fois je le pense.</p>
<p>Comme de nombreux lecteurs, j'ai eu beaucoup de mal à ma nouvelle vie depuis mars dernier. Durant de longs mois dans mon appartement, j'étais perdu, paumé, j'errais du salon à la chambre sans savoir où aller. Je me nourrissais de crasses sans trouver la sortie. J'étais dans un état d'égarement profond, sans repère. Cette situation a duré plusieurs mois, je ne trouvais même plus la direction de la salle de bain. Je te passe les détails. Puis un jour, alors que je regardais par la fenêtre, paralysé sans espoir de sortie de ma prison j'ai eu le déclic. Pour m'en sortir, il me fallait les outils adaptés. C'est pourquoi j'ai ressorti mon <a href="https://www.robotshop.com/eu/en/rplidar-a1m8-360-degree-laser-scanner-development-kit.html">Lidar RPLidar A1M8</a>. Il me fallait une <strong>carte</strong> avec à la clé, la porte de sortie ! Effrayé à l'idée de m'éloigner de la fenêtre moi-même j'ai eu l'idée d'envoyer un ami en exploration. C'est alors que j'ai recommencé à travailler sur mon robot. Non pas <a href="//linuxfr.org/users/jnanar--2/journaux/mon-ami-se-fait-des-amis">Johnny 5</a>, ni <a href="//linuxfr.org/users/jnanar--2/journaux/mon-ami-se-fait-des-amis">R1D1</a> qui ne me donne plus autant satisfaction mais <a href="https://www.agayon.be/">R1D3 (nom de code: Agayon)</a> !</p>
<p>J'avais commencé à travailler sur R1D3 dans l'ancien monde. Avec l'aide d'anciens collègues électroniciens et mécaniciens, je l'avais doté d'un châssis en bois issus d'une caisse de vin, d'engrenages véritables, de courroies de transmission et de deux belles roues de tondeuse. Il a de la gueule mon Agayon. Comme il est plus gros et plus costaud que R1D1, je mets plein de trucs dedans et ça rentre même si rien n'est optimisé.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f7777772e616761796f6e2e62652f696d616765732f723164332f70756c6c65792e6a7067/pulley.jpg" alt="courrois" title="Source : https://www.agayon.be/images/r1d3/pulley.jpg"><br>
<img src="//img.linuxfr.org/img/68747470733a2f2f7777772e616761796f6e2e62652f696d616765732f723164332f6672616d652e6a7067/frame.jpg" alt="chassis" title="Source : https://www.agayon.be/images/r1d3/frame.jpg"><br>
<img src="//img.linuxfr.org/img/68747470733a2f2f7777772e616761796f6e2e62652f696d616765732f723164332f696e736964652e6a7067/inside.jpg" alt="Entrailles" title="Source : https://www.agayon.be/images/r1d3/inside.jpg"></p>
<ul>
<li>A: R1D3 n'est pas un manchot puisqu'il fonctionne au courant continu 12V alimenté par une batterie au plomb d'UPS 7200 mAh. C'est lourd, mais ça va bien.</li>
<li>B: Arduino Mega, le cervelet de l'opération. Une liaison série interagit avec le Raspberry PI et l'autre sert aux mises à jour, au debug, etc.</li>
<li>C: Lignes d'alimentation de 12 et 5V pour les moteurs, des capteurs, des boutons, etc.</li>
<li>D: Raspberry PI4, 4Go RAM, le cerveau. Il est relié à l'Arduino par un adaptateur USB vers port série.</li>
<li>E: Des LEDs qui seront utilisées à l'avenir pour informer des états, du mode engagé etc. Elles sont fonctionnelles, mais pas encore réellement programmées.</li>
<li>F: Des Les boutons (voir ci-dessous)</li>
</ul>
<p>Voici une vue du haut datant d'avant la mise en place du Lidar et de la caméra.<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f626c6f672e616761796f6e2e62652f696d616765732f723164332f726973652f636f6d706c6574655f312e6a7067/complete_1.jpg" alt="Vue du haut" title="Source : https://blog.agayon.be/images/r1d3/rise/complete_1.jpg"></p>
<p>Je lui donne des boutons mais on s'entend bien.</p>
<ul>
<li>Lance roquette: démarrage</li>
<li>Champignon rouge: coupure de l'Arduino et des moteurs</li>
<li>Rectangulaire blanc: à l'avenir il déclenchera l'enregistrement vidéo</li>
<li>Leviers: un des deux servira probablement à lancer un mode démo, à voir</li>
</ul>
<p>Pas mal de chemin a été fait depuis les débuts de Johnny 5. J'ai notamment pu éviter de justesse la <a href="https://fr.wikipedia.org/wiki/Singularit%C3%A9_technologique">singularité technologique</a>, en codant sur le PI en python et puis Arduino quoi. Le problème est qu'en mode autonome, l'Agayon se déplace dans les pièces comme un Rumba en manque d'aspiration. Il a l'air un peu bourré aussi. Il se cogne parfois dans les meubles. Il a cinq capteurs ultrasons, mais il reste des angles morts et le Lidar ne l'avertit pas encore des catastrophes imminentes. Du coup, le plus simple est de le piloter à distances. Mais comme j'avais toujours aussi peur de m'égarer une fois de plus, je lui ai mis une webcam et une interface web pour le piloter via un navigateur web.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f626c6f672e616761796f6e2e62652f696d616765732f723164332f7765622f636170747572655f73747265616d2e706e67/capture_stream.png" alt="Interface web" title="Source : https://blog.agayon.be/images/r1d3/web/capture_stream.png"></p>
<p>Quand <a href="https://www.dillo.org/">Dillo</a> ne suffit plus, je peux le contrôler en bluetooth avec une manette de console. C'est plus réactif et c'est bien pratique pour ramener des bières.</p>
<p>Voilà cher journal je voulais t'écrire pour te dire qu'à partir de maintenant tout va bien aller. Le robot est sorti il y a 20 minutes, il a commencé à scanner mon appartement et j'espère pouvoir sortir très vite. Il reviendra d'un instant à l'autre avec plein de données tout va s'arranger, il va revenir. Il faut qu'il revienne !</p>
<p>… </p>
<p>Sa batterie est vide…</p>
<p>Liens</p>
<ul>
<li>
<a href="https://blog.agayon.be:">https://blog.agayon.be:</a> La liste des pièces est détaillée dans différents articles de blog sous la rubrique "Agayon".</li>
<li><a href="https://www.agayon.be">https://www.agayon.be</a></li>
<li>Le code du PI: <a href="https://gitlab.com/r1d3/rpi">https://gitlab.com/r1d3/rpi</a>
</li>
<li>Le code Arduino: <a href="https://gitlab.com/r1d3/arduino">https://gitlab.com/r1d3/arduino</a>
</li>
<li>Application Flask pour l'API REST: <a href="https://gitlab.com/r1d3/rest_api">https://gitlab.com/r1d3/rest_api</a>
</li>
<li>Template web <a href="https://gitlab.com/r1d3/www_agayon">https://gitlab.com/r1d3/www_agayon</a> (compatible avec <a href="https://github.com/jacksonliam/mjpg-streamer">mjpg_streamer</a> )</li>
</ul>
<p>Pour ceux qui se poseraient la question, l'article ne mentionne pas R1D2. R1D2 est une abomination qui gît quelque part. Il n'était pas viable, cette monstruosité est maintenant un lointain souvenir qui sert de réserve de pièce détachées. Paix à son programme central.</p>
<div><a href="https://linuxfr.org/users/jnanar--2/journaux/un-ami-a-la-carte.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/122737/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/jnanar--2/journaux/un-ami-a-la-carte#comments">ouvrir dans le navigateur</a>
</p>
jnanarhttps://linuxfr.org/nodes/122737/comments.atomtag:linuxfr.org,2005:News/396802020-02-27T16:38:48+01:002020-02-28T11:40:34+01:00Le projet Heptapod : GitLab + Mercurial = 🖤Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Heptapod est une divergence (<em>fork</em>) amicale de GitLab CE qui prend en charge le <a href="https://fr.wikipedia.org/wiki/Mercurial">DVCS Mercurial</a>. Le projet a deux ans et est actuellement disponible sous forme de sources et <a href="https://hub.docker.com/r/octobus/heptapod">d’image Docker à installer</a>.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://heptapod.net/" hreflang="en" href="https://linuxfr.org/redirect/105704">Le site officiel d’Heptapod</a></li><li>lien nᵒ 2 : <a title="https://fosdem.org/2020/schedule/event/heptapod_mercurial/" hreflang="en" href="https://linuxfr.org/redirect/105706">La présentation au FOSDEM 2020</a></li><li>lien nᵒ 3 : <a title="https://heptapod.net/heptapod-080-released.html#heptapod-080-released" hreflang="en" href="https://linuxfr.org/redirect/105707">La présentation au FOSDEM 2020 (diapos)</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-pr%C3%A9sentation">Présentation</a><ul>
<li><a href="#toc-le-projet">Le projet</a></li>
<li><a href="#toc-licence">Licence</a></li>
<li><a href="#toc-relation-avec-gitlab">Relation avec GitLab</a></li>
</ul>
</li>
<li><a href="#toc-fonctionnalit%C3%A9s">Fonctionnalités</a></li>
<li>
<a href="#toc-chantiers-encours">Chantiers en cours</a><ul>
<li><a href="#toc-lavantage-dufork">L’avantage du fork</a></li>
<li><a href="#toc-logiciel-en-tant-que-service-saas-et-instance-publique">Logiciel en tant que service (SaaS) et instance publique</a></li>
<li>
<a href="#toc-traduction-de-lafaq">Traduction de la FAQ</a><ul>
<li><a href="#toc-quel-est-le-prix-du-service-fourni">Quel est le prix du service fourni ?</a></li>
<li><a href="#toc-estil-possible-dh%C3%A9berger-gratuitement-des-projets-comme-on-le-fait-sur-github-ou-gitlab">Est‑il possible d’héberger gratuitement des projets comme on le fait sur GitHub ou GitLab ?</a></li>
<li><a href="#toc-pourquoi-ne-pas-fournir-un-h%C3%A9bergement-gratuit-comme-les-autres-fournisseurs">Pourquoi ne pas fournir un hébergement gratuit comme les autres fournisseurs ?</a></li>
<li><a href="#toc-puisje-disposer-dune-instance-d%C3%A9di%C3%A9e-pour-monorganisation">Puis‑je disposer d’une instance dédiée pour mon organisation ?</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="toc-présentation">Présentation</h2>
<p>GitLab est une application de gestion de dépôts Git sous licence MIT. Elle est <a href="//linuxfr.org/news/gitlab-8-11-vue-kanban-et-bien-plus">régulièrement</a> <a href="//linuxfr.org/news/hebergez-vos-projets-avec-gitlab">mentionnée</a> sur <a href="//linuxfr.org/news/boite-a-outils-pour-gitlab-ci"><em>LinuxFr.org</em></a>. Elle permet d’héberger sur votre propre serveur des dépôts Git avec l’interface Web offrant tout le nécessaire pour vos projets : navigation dans le code source, suivi des demandes de bogues et d’évolutions (« issues »), wiki, gestion des droits d’accès par équipe, commentaires, notifications, etc.</p>
<p><a href="https://www.mercurial-scm.org/">Mercurial</a> est un système de contrôle de version distribué (DVCS) en logiciel libre, écrit principalement en Python, avec une interface de ligne de commande intuitive et des fonctions de réécriture de l’historique solides et sûres. Cependant, Mercurial a manqué quelque peu d’exposition publique au cours des dernières années suite au succès de Git, GitHub et GitLab.</p>
<h3 id="toc-le-projet">Le projet</h3>
<p>Le projet vise à permettre de profiter de toutes les facilités liées à l’utilisation de GitLab, sans être obligé de changer de système de gestion de versions. Il en découle plus de diversité et les auteurs espèrent pouvoir remonter quelques correctifs en amont même si le support de Mercurial dans GitLab CE n’est pas prévu.</p>
<p>Par ailleurs, le projet s’inscrit dans un contexte délicat pour les utilisateurs de forges publiques reposant sur Mercurial. Suite à l’<a href="//linuxfr.org/users/zurvan-0/journaux/bitbucket-abandonne-les-utilisateurs-de-mercurial">abandon de Mercurial par Bitbucket</a>, il est urgent pour les projets l’utilisant de trouver une alternative. Bitbucket a annoncé son intention de ne plus accepter de nouveaux dépôts d’ici le 1<sup>er</sup> février 2020. Les projets existants seront supprimés le premier juin 2020. Heptapod permet de fournir un hébergement pour les projets qui désirent migrer sur une plate‑forme conviviale : GitLab CE.</p>
<p>Heptapod est un projet communautaire dont les principales contributions proviennent de la société <a href="https://octobus.net/">Octobus</a>. </p>
<h3 id="toc-licence">Licence</h3>
<p>Heptapod est publié sous licence <a href="https://heptapod.net/pages/faq.html">MIT Expat license</a>. C’est la même que <a href="https://about.gitlab.com/install/ce-or-ee/">GitLab CE</a> pour ne pas empêcher de possibles correctifs en amont (<em>upstream patches</em>), voire la possibilité de fusionner les branches à l’avenir.</p>
<h3 id="toc-relation-avec-gitlab">Relation avec GitLab</h3>
<p>Le paragraphe suivant est une traduction de la <a href="https://heptapod.net/pages/faq.html">FAQ</a> d’Heptapod.</p>
<p>Octobus serait heureux que GitLab fournisse simplement l’option Mercurial dans un futur proche, car cela correspond à notre activité principale qui est de fournir des services professionnels autour de Mercurial. Cela étant dit, cette question en pratique dépend de la direction que la communauté des Heptapods prendra lorsqu’elle sera mature. Les avantages pour GitLab devront également être clairement démontrés. Pour l’instant, le statut de « <em>fork</em> amical » est assez confortable pour Octobus et pour GitLab.</p>
<h2 id="toc-fonctionnalités">Fonctionnalités</h2>
<ul>
<li>gestion de SSH ;</li>
<li>installation depuis les sources ;</li>
<li>importation de projets depuis Bitbucket.</li>
</ul>
<h2 id="toc-chantiers-encours">Chantiers en cours</h2>
<p>Les priorités du projet sont :</p>
<ul>
<li>montée de version GitLab de 10.5 vers 12.2 ;</li>
<li>rendre la gestion de Mercurial complètement native ; les auteurs pensent obtenir de nets gains de performance et la possibilité de faire des instances distribuées sur une grappe de serveurs ; la qualité du code sera également améliorée ;</li>
<li>fournir une version officielle du GitLab Development Kit — GDK — adaptée à Heptapod ; ils l’appelleront naturellement HDK.</li>
</ul>
<p>Heptapod est un logiciel libre et open source. Si vous voulez qu’il brille, le mieux est d’y contribuer ou d’engager quelqu’un pour le faire. Les développeurs sont demandeurs de contributions externes et ils ont commencé à catégoriser les améliorations qui ne nécessitent pas beaucoup de connaissances. Beaucoup d’entre elles concernent l’expérience utilisateur, alors n’hésitez pas !</p>
<h3 id="toc-lavantage-dufork">L’avantage du fork</h3>
<p>Les divergences entre GitLab et Heptapod sont faibles. Les modifications de Heptapod ne touchent que trois composants :</p>
<ul>
<li>Gitlab Shell ;</li>
<li>Gitaly ;</li>
<li>Gitlab Rails.</li>
</ul>
<p>Au final, le projet bénéficie de toute la force de GitLab en étant porté par une équipe modeste.</p>
<p>Plus d’informations sur le sujet sont disponibles dans la présentation du FOSDEM 2020.</p>
<h3 id="toc-logiciel-en-tant-que-service-saas-et-instance-publique">Logiciel en tant que service (SaaS) et instance publique</h3>
<p>Récemment, Octobus a conclu un accord commercial avec la société <a href="https://www.clever-cloud.com/en/heptapod">Clever Cloud</a> afin de fournir des instances hébergées d’Heptapod.</p>
<h3 id="toc-traduction-de-lafaq">Traduction de la FAQ</h3>
<h4 id="toc-quel-est-le-prix-du-service-fourni">Quel est le prix du service fourni ?</h4>
<p>Nous visons actuellement un prix de base de 7 €/mois par utilisateur avec un supplément basé sur la consommation. Vous paieriez pour le stockage et le trafic sortant, mais seulement ce que vous consommez. Les prévisions de prix actuelles seraient de 0,02 €/Gbit/mois pour le stockage et de 0,09 €/Gbit/mois pour le trafic sortant. N’oubliez pas que rien n’est gravé dans la pierre et que les choses peuvent changer après la phase bêta.</p>
<h4 id="toc-estil-possible-dhéberger-gratuitement-des-projets-comme-on-le-fait-sur-github-ou-gitlab">Est‑il possible d’héberger gratuitement des projets comme on le fait sur GitHub ou GitLab ?</h4>
<p>Il est possible de s’inscrire sur <em><a href="https://foss.heptapod.net">foss.heptapod.net</a></em> et d’y héberger votre projet.</p>
<p>Les <a href="https://heptapod.net/a-public-heptapod-for-free-and-open-source-software.html#a-public-heptapod-for-free-and-open-source-software">critères d’éligibilité</a> y sont détaillés. Les ressources de Clever Cloud et Octobus étant limitées, les projets acceptés doivent satisfaire les critères suivants :</p>
<ul>
<li>projets réellement libres et open source (licence approuvée par l’OSI), et pas seulement publics ;</li>
<li>mentionnent le projet sur leur page Web officielle en insérant des liens et des logos vers Clever Cloud et Octobus à l’endroit approprié, par exemple sur la page qui fournit les instructions de développement ;</li>
<li>ne mettre sur <em>foss.heptapod.net</em> que des dépôts réellement pertinents, la raison étant que nous ne pouvons pas nous permettre d’héberger des milliers de dépôts dont l’utilité serait douteuse, notamment pour éviter les miroirs d’autres forges sans valeur ajoutée et les projets non maintenus.</li>
</ul>
<p>Ces règles sont sujettes à interprétations et vous êtes encouragés à prendre contact avec les sociétés concernées pour plus d’informations.</p>
<h4 id="toc-pourquoi-ne-pas-fournir-un-hébergement-gratuit-comme-les-autres-fournisseurs">Pourquoi ne pas fournir un hébergement gratuit comme les autres fournisseurs ?</h4>
<p>La politique générale de Clever Cloud est que nous ne proposons pas d’hébergement gratuit. Les raisons sont expliquées dans ce <a href="https://www.clever-cloud.com/blog/company/2015/04/14/why-isnt-t-there-a-free-tier-in-clever-cloud/">billet de blog</a>.</p>
<h4 id="toc-puisje-disposer-dune-instance-dédiée-pour-monorganisation">Puis‑je disposer d’une instance dédiée pour mon organisation ?</h4>
<p>Oui, des instances dédiées figurent sur notre feuille de route et seront disponibles ultérieurement. Nous visons actuellement un prix qui la rendrait intéressante pour cinquante utilisateurs ou plus.</p>
<p>Le lien d’inscription est disponible sur la <a href="https://www.clever-cloud.com/en/heptapod">FAQ dédiée</a>.</p>
</div><div><a href="https://linuxfr.org/news/le-projet-heptapod-gitlab-mercurial.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/119329/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/le-projet-heptapod-gitlab-mercurial#comments">ouvrir dans le navigateur</a>
</p>
jnanarAnonymeYsabeau 🧶 🧦gracinetDavy DefaudBAudtheojouedubanjoBenoît Sibaudhttps://linuxfr.org/nodes/119329/comments.atomtag:linuxfr.org,2005:Diary/376792018-01-02T23:20:59+01:002018-01-03T09:45:53+01:00Errol: Envoyer automatiquement des fichiers avec XMPPLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#%C3%80-lorigine">À l'origine</a><ul>
<li><a href="#pourquoi-errol">Pourquoi Errol?</a></li>
</ul>
</li>
<li>
<a href="#howto">Howto</a><ul>
<li>
<a href="#pr%C3%A9requis">Prérequis</a><ul>
<li><a href="#cr%C3%A9er-le-noeud-pubsub">Créer le noeud pubsub</a></li>
<li><a href="#tests">Tests</a></li>
</ul>
</li>
<li><a href="#d%C3%A9marrer-avec-errol">Démarrer avec Errol</a></li>
<li><a href="#installer">Installer</a></li>
<li><a href="#configuration">Configuration</a></li>
<li>
<a href="#voler">Voler</a><ul>
<li><a href="#%C3%80-hogwarts">À Hogwarts</a></li>
<li><a href="#%C3%80-azkaban">À Azkaban</a></li>
</ul>
</li>
<li><a href="#licence">Licence</a></li>
</ul>
</li>
<li>
<a href="#pourquoi-pas-x-or-y">Pourquoi pas X or Y?</a><ul>
<li><a href="#remerciements">Remerciements</a></li>
</ul>
</li>
<li><a href="#liens">Liens</a></li>
</ul><p>Note: ce journal est la traduction d'un article posté sur mon <a href="https://blog.agayon.be/errol.html">blog</a>.</p>
<p>Errol est un programme servant à envoyer des fichiers de machine à machine. Il est basé sur <a href="https://en.wikipedia.org/wiki/Inotify">inotify</a> et le protocole <a href="https://fr.wikipedia.org/wiki/Extensible_Messaging_and_Presence_Protocol">XMPP</a>. Errol peut être utilisé pour surveiller un répertoire et transférer automatiquement les nouveaux fichiers (ou les modifiés) vers une autre machine.</p>
<h2 id="À-lorigine">À l'origine</h2>
<p>Errol a été écrit pour répondre à un besoin. J'ai l'occasion de donner un coup de main à une association locale. Je maintiens leur ERP (<a href="https://www.odoo.com/">Odoo</a>) et une des tâches consiste à générer un tarif biannuel à partir d'un fichier Excel (oui, je sais). Le processus est basé sur la génération d'un fichier <a href="https://www.latex-project.org/">LaTeX</a> car j'ai beaucoup utilisé ce format dans le passé et les PDF générés sont compatibles avec le résultat souhaité. N'ayant pas envie d'installer une distribution LaTeX sur la machine de production, j'ai pris la décision de délocaliser cette tâche sur ma machine personnelle. L'utilisateur téléverse un fichier Excel sur un site web (Django), ce fichier est sauvé dans un répertoire "surveillé" et est ensuite envoyé par XMPP sur ma machine personnelle où le fichier LateX est généré. Le PDF résultat est renvoyé sur le serveur afin d'être pouvoir être téléchargé.</p>
<h3 id="pourquoi-errol">Pourquoi Errol?</h3>
<p>Dans le monde fictionnel de Harry Potter, <a href="https://en.wikipedia.org/wiki/Magical_creatures_in_Harry_Potter#The_Weasleys'_creatures"><strong>Errol</strong></a> est la chouette de la famille Weasley. Il est vieux et maladroit. Certains pourraient dire que XMPP est dans le même état mais Errol (le rapace) trouve son utilité, tout comme XMPP. ;-) Errol est <a href="https://fr.wikipedia.org/wiki/Chouette_lapone">une chouette lapone</a>. Note: en anglais on parle de great grey owl. (voir photos)</p>
<p><a href="https://www.flickr.com/photos/blurredca/10527590684/"><img src="//img.linuxfr.org/img/68747470733a2f2f6672616d617069632e6f72672f674f673432747a713571376d2f68373268786a5871306457392e6a7067/h72hxjXq0dW9.jpg" alt="crédit photo: blurred.ca https://www.flickr.com/photos/blurredca/10527590684/" title="Source : https://framapic.org/gOg42tzq5q7m/h72hxjXq0dW9.jpg"></a><br>
Nimage: <a href="https://www.flickr.com/photos/blurredca/">blurred.ca</a>, Great Grey Owl</p>
<hr><h2 id="howto">Howto</h2>
<h3 id="prérequis">Prérequis</h3>
<p>Errol nécessite l'infrastructure suivante:</p>
<ul>
<li>un système supportant <a href="https://en.wikipedia.org/wiki/Inotify">inotify</a> (Linux).</li>
<li>un serveur XMPP compatible avec les XEPs suivantes: <a href="https://xmpp.org/extensions/xep-0198.html">Stream Management</a>, <a href="https://xmpp.org/extensions/xep-0060.html">Publish-Subscribe</a>, <a href="https://xmpp.org/extensions/xep-0045.html">Multi-User Chat</a>
</li>
<li>un service pubsub permettant d'avoir des nœuds ouverts. Le nom du nœud est renseigné dans le fichier de configuration. Mon service est <a href="https://blog.agayon.be/sat_pubsub.html">sat_pubsub</a>, le composant pubsub du projet <a href="https://salut-a-toi.org/">Salut à Toi</a>.</li>
<li>un chat multi-utilisateurs (MUC) pour assurer la compatibilité avec certains clients et le débogage. À l'avenir, le MUC pourrait devenir obsolète.</li>
</ul><p>Il est possible d'utiliser son propre serveur ou d'utiliser un service de la <a href="https://conversations.im/compliance/">liste suivante</a>.</p>
<h4 id="créer-le-noeud-pubsub">Créer le noeud pubsub</h4>
<p>Cette étape est optionnelle si vous disposez déjà d'un accès en écriture sur un nœud (ex: nœud de blogage). L'exemple suivant montre la création du nœud avec <a href="https://blog.agayon.be/sat_jp.html">jp</a>, l'interface en ligne de commande de Salut à Toi. Les alternatives <a href="https://dev.louiz.org/projects/slixmpp">Slixmpp</a> ou <a href="https://github.com/fritzy/SleekXMPP">Sleekxmpp</a> peuvent être utilisés grâce à leurs scripts d'exemples.</p>
<pre><code>$ jp pubsub node create -f publish_model open be.agayon.errol:0 -s pubsub.agayon.be -c
</code></pre>
<p>Le nom du nœud recommandé est be.agayon.errol:0 afin d'identifier la fonctionnalité. </p>
<p>En guise d'exemple, voici les informations de mon nœud sur pubsub.agayon.be:</p>
<pre><code>$ jp pubsub node info be.agayon.errol:0 -s pubsub.agayon.be
persist_items: True
deliver_payloads: True
serial_ids: False
publish_model: open
access_model: open
send_last_published_item: on_sub
</code></pre>
<p>Si votre serveur supporte <a href="https://xmpp.org/extensions/xep-0163.html">Personal Eventing Protocol</a>(PEP) ou si vous ne voulez pas utiliser un service pubsub dédié, vous pouvez utiliser un nœud de microbologage (urn:xmpp:microblog:0) et votre propre jid (adresse xmpp) pour suivre les informations dans <a href="https://movim.eu/">Movim</a> ou <a href="https://salut-a-toi.org/">Salut à Toi</a>.</p>
<pre><code class="sh">$ jp pubsub node create -f publish_model open urn:xmpp:microblog:0 -s info@agayon.be -c</code></pre>
<h4 id="tests">Tests</h4>
<p>Vous pouvez tester votre installation avec les scripts d'exemples de <a href="https://git.poez.io/slixmpp">slixmpp</a>.</p>
<ul>
<li><a href="https://git.poez.io/slixmpp/tree/examples/pubsub_client.py">pubsub_client.py</a></li>
<li><a href="https://git.poez.io/slixmpp/tree/examples/pubsub_events.py">pubsub_events.py</a></li>
<li><a href="https://git.poez.io/slixmpp/tree/examples/s5b_transfer/s5b_receiver.py">s5b_receiver.py</a></li>
<li><a href="https://git.poez.io/slixmpp/tree/examples/s5b_transfer/s5b_sender.py">s5b_sender.py</a></li>
</ul><p>Exemple:<br><code><br>
./s5b_file_sender.py -j jid@example.org -p pass -r john@example.org -f /path/to/file.txt <br></code></p>
<p>Les scripts fournissent plus d'information.</p>
<h3 id="démarrer-avec-errol">Démarrer avec Errol</h3>
<p>Errol a besoin des dépendances suivantes:</p>
<ul>
<li>
<a href="https://dev.louiz.org/projects/slixmpp">slixmpp</a> (python 3)</li>
<li><a href="https://docs.python.org/3/library/asyncio.html">asyncio</a></li>
<li><a href="https://docs.python.org/3/library/configparser.html">configparser</a></li>
<li><a href="https://github.com/rbarrois/aionotify">aionotify</a></li>
</ul><h3 id="installer">Installer</h3>
<p>Errol s'installe très bien dans un <a href="https://virtualenv.pypa.io/en/stable/userguide/">virtualenv</a> mais ce n'est pas obligatoire.</p>
<pre><code>$ pip install errol
</code></pre>
<p>Ou encore, après avoir cloné le dépôt:</p>
<pre><code class="sh"> $ git clone https://gitlab.com/jnanar/errol.git
$ <span class="nb">cd</span> errol
$ python3 setup.py install</code></pre>
<h3 id="configuration">Configuration</h3>
<p>Vous devez remplir un fichier de configuration pour fournir les informations suivantes:</p>
<pre><code>$ cat config.example.ini
[XMPP]
pubsub=pubsub.example.org
node=be.agayon.errol:0
room=chat@chat.example.org
jid=jid@example.org/errol
password=pass
ressource_receiver=-receiver
ressource_sender=-
nick_sender=example_sender
nick_receiver=example_receiver
receiver=jid@example.org/errol-receiver
</code></pre>
<ul>
<li>jid : le compte XMPP</li>
<li>password: le mot de passe du compte</li>
<li>pubsub: le serveur pubsub (peut-être le jid)</li>
<li>room: le salon MUC (chatroom)</li>
</ul><p>Dans cet exemple, les fichiers seront envoyés par <strong><a href="mailto:jid@example.org">jid@example.org</a>/errol-0</strong> à <strong><a href="mailto:jid@example.org">jid@example.org</a>/errol-receiver</strong>.<br>
Les "nicks" sont les pseudos utilisés dans le MUC.</p>
<h3 id="voler">Voler</h3>
<p><a href="https://www.flickr.com/photos/widnr/8716853721/"><img src="//img.linuxfr.org/img/68747470733a2f2f6672616d617069632e6f72672f4766393077716367556b46582f574263506539684f6a476f552e6a7067/WBcPe9hOjGoU.jpg" alt="Photo credit: Wisconsin Department of Natural Resources https://www.flickr.com/photos/widnr/" title="Source : https://framapic.org/Gf90wqcgUkFX/WBcPe9hOjGoU.jpg"></a><br>
Nimage: <a href="https://www.flickr.com/photos/widnr/">Wisconsin Department of Natural Resources</a>, Great Grey Owl at Mauston</p>
<p>Une fois installé, Errol est utilisable depuis le terminal.</p>
<pre><code>$ errol --help
usage: errol [-h] [-e EVENTS] [-f FILE] [-d] -p PATH -c COMMAND
Automatic XMPP file sender and directory watcher
optional arguments:
-h, --help show this help message and exit
-e EVENTS, --events EVENTS
Number of events to watch (delete, create modify) in
the directory. Once reached, the program stops.
-f FILE, --file FILE Config file containing XMPP parameters
-d, --debug set logging to DEBUG
-p PATH, --path PATH The path watched.
-c COMMAND, --command COMMAND
The executed command: xmpp or watcher
</code></pre>
<h4 id="À-hogwarts">À Hogwarts</h4>
<p>Pour surveiller le répertoire <code>/tmp/sender</code>, il faut utiliser la commande suivante:</p>
<pre><code>$ errol -f config.example.ini -p /tmp/sender -c watcher
</code></pre>
<p>Tous les fichiers nouvellement créés ou modifiés seront envoyés.</p>
<h4 id="À-azkaban">À Azkaban</h4>
<p>Pour recevoir les fichiers dans le dossier <code>/tmp/receiver</code>, il faut lancer errol comme suit:</p>
<pre><code>$ errol -f config.example.ini -p /tmp/receiver -c xmpp
</code></pre>
<h3 id="licence">Licence</h3>
<p>Le projet est libéré sous GPLv3.</p>
<h2 id="pourquoi-pas-x-or-y">Pourquoi pas X or Y?</h2>
<p><a href="https://www.flickr.com/photos/volvob12b/37310719232/"><img src="//img.linuxfr.org/img/68747470733a2f2f6672616d617069632e6f72672f3863726a4155374661764a442f42436d3873576b72797574742e6a7067/BCm8sWkryutt.jpg" alt="Photo credit: Bernard Spragg. NZ https://www.flickr.com/photos/volvob12b/37310719232/" title="Source : https://framapic.org/8crjAU7FavJD/BCm8sWkryutt.jpg"></a><br>
Nimage: <a href="https://www.flickr.com/photos/volvob12b/">Bernard Spragg. NZ</a>, Great Grey Owl (Strix nebulosa)</p>
<p>Il y a 36 manière de solutionner ce problème pour ce type de besoins. Certaines sont plus matures, plus connues ou évidentes. J'ai choisi XMPP pour plusieurs raisons:</p>
<ul>
<li>
<a href="https://agayon.be">agayon.be</a> possède déjà un service XMPP à jour avec les XEPs nécessaires activées.</li>
<li>Je voulais apprendre à travailler avec pubsub pour des communications machines à machines et utiliser les notifications (pourquoi pas?).</li>
</ul><p>Parmi les alternatives, le service aurait pu être basé sur </p>
<ul>
<li>des sockets</li>
<li>un transfert de fichier HTTP.</li>
<li>API REST </li>
<li>SSH et commandes à distances</li>
<li>…</li>
</ul><h3 id="remerciements">Remerciements</h3>
<p><a href="https://www.flickr.com/photos/115391424@N05/36873334554/"><img src="//img.linuxfr.org/img/68747470733a2f2f6672616d617069632e6f72672f6742783268796636524141462f55474c44685a4c346e7877732e6a7067/UGLDhZL4nxws.jpg" alt="Photo credit: lasta29, Great grey owl, Osaka Tennoji Zoo https://www.flickr.com/photos/115391424@N05/36873334554/" title="Source : https://framapic.org/gBx2hyf6RAAF/UGLDhZL4nxws.jpg"></a><br>
Nimage: <a href="https://www.flickr.com/photos/115391424@N05/">lasta29</a>, Great grey owl, Osaka Tennoji Zoo</p>
<ul>
<li>
<a href="https://github.com/poezio/slixmpp">Slixmpp</a> pour la bibliothèque sympa.</li>
<li>La communauté XMPP francophone (<a href="mailto:sat@chat.jabberfr.org">sat@chat.jabberfr.org</a>, <a href="mailto:jabberfr@chat.jabberfr.org">jabberfr@chat.jabberfr.org</a>)</li>
<li>Link Mauve (<a href="https://jabberfr.org/">JabberFR</a>).</li>
<li>Goffi (<a href="https://salut-a-toi.org/">Salut à Toi</a>).</li>
</ul><h2 id="liens">Liens</h2>
<ul>
<li><a href="https://gitlab.com/jnanar/errol">Dépôt git</a></li>
<li><a href="https://salut-a-toi.org/">Salut à Toi</a></li>
<li><a href="https://docs.python.org/3/library/asyncio.html">asyncio </a></li>
<li><a href="https://dev.louiz.org/projects/slixmpp">slixmpp</a></li>
<li><a href="https://github.com/rbarrois/aionotify">aionotify</a></li>
</ul><div><a href="https://linuxfr.org/users/jnanar--2/journaux/errol-envoyer-automatiquement-des-fichiers-avec-xmpp.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/113428/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/jnanar--2/journaux/errol-envoyer-automatiquement-des-fichiers-avec-xmpp#comments">ouvrir dans le navigateur</a>
</p>
jnanarhttps://linuxfr.org/nodes/113428/comments.atomtag:linuxfr.org,2005:News/380092017-06-09T09:30:08+02:002017-06-09T11:09:31+02:00Prédire la note d’un journal sur LinuxFr.orgLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Cette dépêche traite de l’exploration de données sur des données issues de <em>LinuxFr.org</em>.</p>
<p>Ayant découvert récemment <a href="https://fr.wikipedia.org/wiki/Scikit-learn"><em>scikit-learn</em></a>, une bibliothèque Python d’apprentissage statistique (<em>machine learning</em>). Je voulais partager ici un début d’analyse sur des contenus issus de <em>LinuxFr.org</em>.</p>
<p><em>Avertissement : je ne suis pas programmeur, ni statisticien. Je ne maîtrise pas encore tous les arcanes de scikit-learn et de nombreux éléments théoriques m’échappent encore. Je pense néanmoins que les éléments présentés ici pourront en intéresser plus d’un(e).</em></p>
<p>Tous les scripts sont codés en Python et l’analyse à proprement parler a été réalisée à l’aide d’un <em>notebook</em> <a href="https://fr.wikipedia.org/wiki/Jupyter">Jupyter</a>. Un dépôt contenant les données et les scripts est disponible sur <a href="https://gitlab.com/jnanar/scikit-linuxfr"><em>GitLab</em></a>.</p></div><ul><li>lien nᵒ 1 : <a title="https://gitlab.com/jnanar/scikit-linuxfr" hreflang="en" href="https://linuxfr.org/redirect/99902">Dépôt GitLab de l’analyse</a></li><li>lien nᵒ 2 : <a title="http://scikit-learn.org/" hreflang="en" href="https://linuxfr.org/redirect/99903">Scikit-learn</a></li><li>lien nᵒ 3 : <a title="https://www.crummy.com/software/BeautifulSoup/" hreflang="en" href="https://linuxfr.org/redirect/99904">Beautifulsoup</a></li><li>lien nᵒ 4 : <a title="http://pandas.pydata.org/" hreflang="en" href="https://linuxfr.org/redirect/99985">Python Data Analysis Library (pandas)</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#pr%C3%A9dire-la-note-dun-journal">Prédire la note d’un journal</a></li>
<li>
<a href="#obtenir-les-donn%C3%A9es">Obtenir les données</a><ul>
<li><a href="#approche-1-le-flux-atom">Approche 1: le flux atom</a></li>
<li><a href="#approche-2-lheure-de-la-soupe">Approche 2: l'heure de la soupe</a></li>
</ul>
</li>
<li>
<a href="#analyse-des-donn%C3%A9es">Analyse des données</a><ul>
<li><a href="#laffaire-est-dans-le-sac-de-mots">L’affaire est dans le sac (de mots)</a></li>
<li><a href="#utiliser-les-fr%C3%A9quences-dapparition-des-mots">Utiliser les fréquences d'apparition des mots</a></li>
</ul>
</li>
<li>
<a href="#classifier-les-articles">Classifier les articles</a><ul>
<li>
<a href="#approche-na%C3%AFve--filtrage-bay%C3%A9sien">Approche naïve : filtrage bayésien</a><ul>
<li><a href="#tester-le-mod%C3%A8le-avec-les-journaux-connus">Tester le modèle avec les journaux connus</a></li>
</ul>
</li>
<li><a href="#support-vector-machine-svm">Support vector machine (SVM)</a></li>
</ul>
</li>
<li><a href="#validation-crois%C3%A9e">Validation croisée</a></li>
<li>
<a href="#optimisation-des-param%C3%A8tres">Optimisation des paramètres</a><ul>
<li><a href="#test-sur-un-%C3%A9chantillon-de-donn%C3%A9es-connues">Test sur un échantillon de données connues</a></li>
<li><a href="#test-sur-un-%C3%A9chantillon-de-donn%C3%A9es-inconnues">Test sur un échantillon de données inconnues</a></li>
</ul>
</li>
<li>
<a href="#utiliser-des-propri%C3%A9t%C3%A9s-multiples">Utiliser des propriétés multiples</a><ul>
<li><a href="#extraction-et-pr%C3%A9paration-des-donn%C3%A9es">Extraction et préparation des données</a></li>
<li><a href="#conversion-des-dates">Conversion des dates</a></li>
<li><a href="#%C3%89volution-du-score-des-journaux-au-fil-du-temps">Évolution du score des journaux au fil du temps</a></li>
<li>
<a href="#calcul-de-l%C3%A2ge-dun-compte">Calcul de l’âge d’un compte</a><ul>
<li><a href="#qualit%C3%A9-des-posts-des-nouveaux">Qualité des posts des nouveaux</a></li>
</ul>
</li>
<li><a href="#calcul-de-la-moyenne-des-scores-pr%C3%A9c%C3%A9dents">Calcul de la moyenne des scores précédents</a></li>
<li><a href="#garder-lessentiel">Garder l’essentiel</a></li>
</ul>
</li>
<li>
<a href="#lunion-fait-la-force">L’union fait la force</a><ul>
<li><a href="#validation-crois%C3%A9e-1">Validation croisée</a></li>
</ul>
</li>
<li><a href="#donn%C3%A9es-hors-%C3%A9chantillon">Données hors échantillon.</a></li>
<li><a href="#pour-aller-plus-loin">Pour aller plus loin</a></li>
<li><a href="#conclusions">Conclusions</a></li>
<li><a href="#perspectives">Perspectives</a></li>
<li><a href="#r%C3%AAvons-un-peu">Rêvons un peu</a></li>
<li><a href="#note">Note</a></li>
</ul><h2 id="prédire-la-note-dun-journal">Prédire la note d’un journal</h2>
<p>Il y a eu récemment une vague de journaux politiques sur DLFP. La note de la plupart de ces journaux était assez basse. Par ailleurs, on lit régulièrement ici des personnes qui se plaignent de la note de leurs articles. Bien souvent, des gens postent des contenus incendiaires, parfois en rafale. Je me suis demandé si cela est évitable. </p>
<p><em>Est-il possible de prédire la note d'un journal en fonction de son contenu?</em> Le problème est ambitieux mais il permettrait aux auteurs d'avoir une idée de l’accueil qui sera réservé à leur prose.</p>
<p>Prédire un score me paraît hasardeux, c'est pourquoi j'ai préféré classer les journaux dans 4 catégories en fonction de leur note, <strong>n</strong> (en <em>english</em> car il est bien connu que ça <em>improve</em> la <em>productivitaÿ</em>) :</p>
<ul>
<li>n < -20 : <em>Magnificent Troll</em> ;</li>
<li>-20 < n < 0 : <em>Great Troll</em> ;</li>
<li>0 < n < 20 : <em>Average Troll</em> ;</li>
<li>20 < n : <em>Qualitaÿ Troll</em>.</li>
</ul><p>Vous l'aurez compris, tout contenu est un <em>Troll</em>, car je pense que nous sommes tous le troll d'un autre.</p>
<h2 id="obtenir-les-données">Obtenir les données</h2>
<p>Il n'existe pas à ma connaissance de base de données de DLFP disponible pour tests. Après avoir lu <a href="//linuxfr.org/users/krunch/journaux/rapport-signal-bruit-et-filtre-passe-haut">deux</a> <a href="//linuxfr.org/users/palkeo/journaux/de-la-prediction-de-l-auteur-d-un-journal-sur-linuxfr">journaux</a> précédents, j'ai décidé de construire une moulinette afin d'aspirer une partie du contenu.</p>
<h3 id="approche-1-le-flux-atom">Approche 1: le flux atom</h3>
<p>Dans un premier temps, j'ai utilisé le flux atom des journaux à l'aide de la bibliothèque <a href="https://pypi.python.org/pypi/feedparser">feedparser</a>. Le <a href="https://gitlab.com/jnanar/scikit-linuxfr/blob/master/atom_to_csv.py">script</a> fonctionne et l'approche est très simple mais malheureusement, la quantité de données est trop limitées. Par ailleurs, le score d'un contenu n'est pas disponible dans les flux. J'ai donc changé mon fusil d'épaule.</p>
<h3 id="approche-2-lheure-de-la-soupe">Approche 2: l'heure de la soupe</h3>
<p>Afin d'augmenter le volume de données, il faut parcourir la page <a href="//linuxfr.org/journaux?page=x">https://linuxfr.org/journaux?page=x</a> et collecter tous les liens vers les différents journaux. Chaque journal est ensuite analysé. Dans un premier temps, les informations suivantes sont utilisées : le nom de l'auteur, le titre du journal, l'URL, le contenu du journal, sa note.</p>
<p>La <a href="https://gitlab.com/jnanar/scikit-linuxfr/blob/master/linuxfr_parser.py">moulinette</a> s'appuie sur la bibliothèque <a href="https://pypi.python.org/pypi/beautifulsoup4/4.6.0">Beautiful Soup4</a>. Les données sont enregistrées dans un fichier CSV. Étant donné que le contenu des journaux est très varié, j'ai choisi les caractères µ et £ en tant que délimiteur et séparateur, respectivement.</p>
<h2 id="analyse-des-données">Analyse des données</h2>
<p>L'analyse suivante est réalisée à l'aide du fichier <a href="https://gitlab.com/jnanar/scikit-linuxfr/blob/master/diaries_classification.ipynb">diaries_classification.ipynb</a>. La lecture du fichier CSV <code>linuxfr.csv</code> montre qu'il contient 5921 journaux. 302 Magnificents Trolls, 460 Great Trolls, 2545 Quality Trolls et 2614 Average Trolls. Étant donné que les données sont déséquilibrées, il faudra en tenir compte dans les travaux car ces chiffres influencent les probabilités. </p>
<pre><code class="python"><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="kn">as</span> <span class="nn">plt</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="kn">as</span> <span class="nn">pd</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="kn">as</span> <span class="nn">np</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">matplotlib</span>
<span class="c1"># Enable inline plotting</span>
<span class="o">%</span><span class="n">matplotlib</span> <span class="n">inline</span>
<span class="n">filename</span> <span class="o">=</span> <span class="sa">r</span><span class="s1">'linuxfr.csv'</span>
<span class="n">lf_data</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"UTF-8"</span><span class="p">,</span> <span class="n">sep</span><span class="o">=</span><span class="s1">'£'</span><span class="p">,</span> <span class="n">engine</span><span class="o">=</span><span class="s1">'python'</span><span class="p">,</span> <span class="n">quotechar</span><span class="o">=</span><span class="s1">'µ'</span><span class="p">)</span></code></pre>
<pre><code class="python"><span class="nb">len</span><span class="p">(</span><span class="n">lf_data</span><span class="p">)</span>
<span class="mi">5921</span></code></pre>
<pre><code class="python"><span class="n">lf_data</span><span class="o">.</span><span class="n">quality_content</span><span class="o">.</span><span class="n">value_counts</span><span class="p">()</span>
<span class="n">Average</span> <span class="n">Troll</span> <span class="mi">2614</span>
<span class="n">Quality</span> <span class="n">Troll</span> <span class="mi">2545</span>
<span class="n">Great</span> <span class="n">Troll</span> <span class="mi">460</span>
<span class="n">Magnificent</span> <span class="n">Troll</span> <span class="mi">302</span>
<span class="n">Name</span><span class="p">:</span> <span class="n">quality_content</span><span class="p">,</span> <span class="n">dtype</span><span class="p">:</span> <span class="n">int64</span></code></pre>
<pre><code class="python"><span class="n">lf_data</span><span class="o">.</span><span class="n">quality_content</span><span class="o">.</span><span class="n">value_counts</span><span class="p">()</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">kind</span><span class="o">=</span><span class="s1">'bar'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s1">'Occurences'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">yticks</span><span class="p">(</span><span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s1">'Trolls'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">xticks</span><span class="p">(</span><span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span></code></pre>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6e6272655f6a6f75726e6175785f312e706e67/nbre_journaux_1.png" alt="Analyse des journaux" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/nbre_journaux_1.png"></p>
<p>Au passage, on observe qu'il y a beaucoup plus de contenu de qualité (pertinent), dont le score est positif que de négatif. Ou encore, qu'il y a beaucoup plus de contenu avec lequel les votants sont d'accord.</p>
<h3 id="laffaire-est-dans-le-sac-de-mots">L’affaire est dans le sac (de mots)</h3>
<p>À ce stade, j'ai suivi la documentation <a href="http://scikit-learn.org/stable/tutorial/text_analytics/working_with_text_data.html">officielle de scikit-learn</a>. L'analyse de texte est le plus souvent basée sur un algorithme de type <a href="https://fr.wikipedia.org/wiki/Sac_de_mots">"Bag of words"</a>. Chaque mot est compté dans le texte. On est alors en mesure de tracer un histogramme du nombre d’occurrence des mots en fonction de la liste des mots du dictionnaire. Dans scikit-learn, l'utilisation d'un sac de mots est très simple. Il faut faire appel à la classe CountVectorizer. Ma base de 5921 journaux contient 78879 mots différents.</p>
<pre><code class="python"><span class="kn">import</span> <span class="nn">numpy</span> <span class="kn">as</span> <span class="nn">np</span>
<span class="kn">from</span> <span class="nn">sklearn.feature_extraction.text</span> <span class="kn">import</span> <span class="n">CountVectorizer</span>
<span class="n">count_vect</span> <span class="o">=</span> <span class="n">CountVectorizer</span><span class="p">()</span>
<span class="n">X_train_counts</span> <span class="o">=</span> <span class="n">count_vect</span><span class="o">.</span><span class="n">fit_transform</span><span class="p">(</span><span class="n">lf_data</span><span class="p">[</span><span class="s1">'content'</span><span class="p">]</span><span class="o">.</span><span class="n">values</span><span class="p">)</span>
<span class="n">X_train_counts</span><span class="o">.</span><span class="n">shape</span>
<span class="p">(</span><span class="mi">5921</span><span class="p">,</span> <span class="mi">78879</span><span class="p">)</span></code></pre>
<h3 id="utiliser-les-fréquences-dapparition-des-mots">Utiliser les fréquences d'apparition des mots</h3>
<p>L'inconvénient du comptage de mots est qu'il entraîne un déséquilibre entre les textes de longueur différente. Il est possible de calculer les fréquences (tf) et éventuellement diminuer l'impact des mots qui apparaissent dans beaucoup de documents tels que les pronoms (tf-idf). L'utilisation de ces algorithmes est tout aussi simple :</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.feature_extraction.text</span> <span class="kn">import</span> <span class="n">TfidfTransformer</span>
<span class="n">tf_transformer</span> <span class="o">=</span> <span class="n">TfidfTransformer</span><span class="p">(</span><span class="n">use_idf</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">X_train_counts</span><span class="p">)</span>
<span class="n">X_train_tf</span> <span class="o">=</span> <span class="n">tf_transformer</span><span class="o">.</span><span class="n">transform</span><span class="p">(</span><span class="n">X_train_counts</span><span class="p">)</span></code></pre>
<h2 id="classifier-les-articles">Classifier les articles</h2>
<h3 id="approche-naïve--filtrage-bayésien">Approche naïve : filtrage bayésien</h3>
<p>La manière la plus simple d'analyser les articles est d'utiliser la <a href="https://fr.wikipedia.org/wiki/Classification_na%C3%AFve_bay%C3%A9sienne">classification naïve bayésienne</a>. Wikipedia éclaire un peu plus les concepts sous-jacents :</p>
<blockquote>
<p>En termes simples, un classificateur bayésien naïf suppose que l'existence d'une caractéristique pour une classe, est indépendante de l'existence d'autres caractéristiques. Un fruit peut être considéré comme une pomme s'il est rouge, arrondi, et fait une dizaine de centimètres. Même si ces caractéristiques sont liées dans la réalité, un classificateur bayésien naïf déterminera que le fruit est une pomme en considérant indépendamment ces caractéristiques de couleur, de forme et de taille.</p>
</blockquote>
<p>Une fois le modèle entraîné (fonction <em>fit</em>, d'adéquation en français), il est possible de prédire à quelle catégorie des articles appartiennent. </p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.naive_bayes</span> <span class="kn">import</span> <span class="n">MultinomialNB</span>
<span class="n">classifier</span> <span class="o">=</span> <span class="n">MultinomialNB</span><span class="p">()</span>
<span class="n">classifier</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">X_train_tfidf</span><span class="p">,</span> <span class="n">targets</span><span class="p">)</span>
<span class="n">training_journals</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'Sécuriser son serveur avec la commande sudo rm -rf /*'</span><span class="p">,</span>
<span class="s1">'Debian is dying'</span><span class="p">,</span>
<span class="s1">'Windows Millenium est meilleur que Linux sur calculatrice graphique'</span><span class="p">,</span>
<span class="s2">"MultiDeskOS est 42% plus performant que Redhat 3.0.3 (Picasso)"</span><span class="p">,</span>
<span class="s2">"Pierre Tramo président !"</span><span class="p">,</span>
<span class="s2">"Des chocolatines au menu des cantines situées dans les DOM-TOM"</span><span class="p">,</span>
<span class="s2">"1515, l’année du Desktop Linux!"</span><span class="p">]</span>
<span class="n">X_new_counts</span> <span class="o">=</span> <span class="n">count_vect</span><span class="o">.</span><span class="n">transform</span><span class="p">(</span><span class="n">training_journals</span><span class="p">)</span>
<span class="n">X_new_tfidf</span> <span class="o">=</span> <span class="n">tfidf_transformer</span><span class="o">.</span><span class="n">transform</span><span class="p">(</span><span class="n">X_new_counts</span><span class="p">)</span>
<span class="n">predicted</span> <span class="o">=</span> <span class="n">classifier</span><span class="o">.</span><span class="n">predict</span><span class="p">(</span><span class="n">X_new_tfidf</span><span class="p">)</span>
<span class="k">for</span> <span class="n">doc</span><span class="p">,</span> <span class="n">category</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">training_journals</span><span class="p">,</span> <span class="n">predicted</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'</span><span class="si">%r</span><span class="s1"> => </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="p">(</span><span class="n">doc</span><span class="p">,</span> <span class="n">category</span><span class="p">))</span></code></pre>
<pre><code>'Sécuriser son serveur avec la commande sudo rm -rf /*' => Quality Troll
'Debian is dying' => Quality Troll
'Windows Millenium est meilleur que Linux sur calculatrice graphique' => Quality Troll
'MultiDeskOS est 42% plus performant que Redhat 3.0.3 (Picasso)' => Average Troll
'Pierre Tramo président !' => Average Troll
'Des chocolatines au menu des cantines situées dans les DOM-TOM' => Quality Troll
'1515, l’année du Desktop Linux!' => Average Troll
</code></pre>
<p>La commande <code>predict_proba</code> permet d'afficher les probabilités. Il en ressort que la marge d'erreur est énorme.</p>
<pre><code class="python"><span class="n">predicted_proba</span> <span class="o">=</span> <span class="n">classifier</span><span class="o">.</span><span class="n">predict_proba</span><span class="p">(</span><span class="n">X_new_tfidf</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">targets_names</span><span class="p">)</span>
<span class="n">predicted_proba</span>
<span class="p">[</span><span class="s1">'Average Troll'</span><span class="p">,</span> <span class="s1">'Great Troll'</span><span class="p">,</span> <span class="s1">'Magnificent Troll'</span><span class="p">,</span> <span class="s1">'Quality Troll'</span><span class="p">]</span>
<span class="n">array</span><span class="p">([[</span> <span class="mf">0.38146407</span><span class="p">,</span> <span class="mf">0.01242555</span><span class="p">,</span> <span class="mf">0.00699732</span><span class="p">,</span> <span class="mf">0.59911306</span><span class="p">],</span>
<span class="p">[</span> <span class="mf">0.45180296</span><span class="p">,</span> <span class="mf">0.03300345</span><span class="p">,</span> <span class="mf">0.01880854</span><span class="p">,</span> <span class="mf">0.49638505</span><span class="p">],</span>
<span class="p">[</span> <span class="mf">0.37809693</span><span class="p">,</span> <span class="mf">0.0190014</span> <span class="p">,</span> <span class="mf">0.00917897</span><span class="p">,</span> <span class="mf">0.5937227</span> <span class="p">],</span>
<span class="p">[</span> <span class="mf">0.47083803</span><span class="p">,</span> <span class="mf">0.0629247</span> <span class="p">,</span> <span class="mf">0.02837355</span><span class="p">,</span> <span class="mf">0.43786371</span><span class="p">],</span>
<span class="p">[</span> <span class="mf">0.54130358</span><span class="p">,</span> <span class="mf">0.04642992</span><span class="p">,</span> <span class="mf">0.03861831</span><span class="p">,</span> <span class="mf">0.37364818</span><span class="p">],</span>
<span class="p">[</span> <span class="mf">0.45172753</span><span class="p">,</span> <span class="mf">0.03297976</span><span class="p">,</span> <span class="mf">0.01805764</span><span class="p">,</span> <span class="mf">0.49723507</span><span class="p">],</span>
<span class="p">[</span> <span class="mf">0.59237292</span><span class="p">,</span> <span class="mf">0.01164186</span><span class="p">,</span> <span class="mf">0.00420374</span><span class="p">,</span> <span class="mf">0.39178148</span><span class="p">]])</span></code></pre>
<p>Mes "journaux" sont beaucoup trop courts pour être représentatifs, enfin cela dépend de la définition de "contenu de qualité". Par conséquent, il faut tester le modèle sur l'archive des contenus, dans un premier temps. Pour y arriver, je définis un pipeline qui consiste à assembler les étapes décrites précédemment dans un objet qui se comporte comme un classificateur.</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.pipeline</span> <span class="kn">import</span> <span class="n">Pipeline</span>
<span class="n">text_clf</span> <span class="o">=</span> <span class="n">Pipeline</span><span class="p">([(</span><span class="s1">'vect'</span><span class="p">,</span> <span class="n">CountVectorizer</span><span class="p">()),</span>
<span class="p">(</span><span class="s1">'tfidf'</span><span class="p">,</span> <span class="n">TfidfTransformer</span><span class="p">()),</span>
<span class="p">(</span><span class="s1">'clf'</span><span class="p">,</span> <span class="n">MultinomialNB</span><span class="p">()),])</span></code></pre>
<h4 id="tester-le-modèle-avec-les-journaux-connus">Tester le modèle avec les journaux connus</h4>
<p>Je commence par échantillonner 20 % des journaux de la base de données et je teste le modèle sur cet ensemble, afin de voir s'il est capable de retrouver la bonne catégorie.</p>
<pre><code class="python"><span class="n">diaries_test</span> <span class="o">=</span> <span class="n">lf_data</span><span class="o">.</span><span class="n">sample</span><span class="p">(</span><span class="n">frac</span><span class="o">=</span><span class="mf">0.2</span><span class="p">)</span>
<span class="n">predicted</span> <span class="o">=</span> <span class="n">text_clf</span><span class="o">.</span><span class="n">predict</span><span class="p">(</span><span class="n">diaries_test</span><span class="p">[</span><span class="s1">'quality_content'</span><span class="p">])</span></code></pre>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.metrics</span> <span class="kn">import</span> <span class="n">confusion_matrix</span><span class="p">,</span> <span class="n">f1_score</span>
<span class="n">score</span> <span class="o">=</span> <span class="n">f1_score</span><span class="p">(</span><span class="n">diaries_test</span><span class="p">[</span><span class="s1">'quality_content'</span><span class="p">],</span> <span class="n">predicted</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="s1">'weighted'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Diaries:'</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">diaries_test</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Score:'</span><span class="p">,</span> <span class="n">score</span><span class="p">)</span>
<span class="n">Diaries</span><span class="p">:</span> <span class="mi">5921</span>
<span class="n">Score</span><span class="p">:</span> <span class="mf">0.269979533821</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">python3</span><span class="o">.</span><span class="mi">6</span><span class="o">/</span><span class="n">site</span><span class="o">-</span><span class="n">packages</span><span class="o">/</span><span class="n">sklearn</span><span class="o">/</span><span class="n">metrics</span><span class="o">/</span><span class="n">classification</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">1113</span><span class="p">:</span> <span class="n">UndefinedMetricWarning</span><span class="p">:</span> <span class="n">F</span><span class="o">-</span><span class="n">score</span> <span class="ow">is</span> <span class="n">ill</span><span class="o">-</span><span class="n">defined</span> <span class="ow">and</span> <span class="n">being</span> <span class="nb">set</span> <span class="n">to</span> <span class="mf">0.0</span> <span class="ow">in</span> <span class="n">labels</span> <span class="k">with</span> <span class="n">no</span> <span class="n">predicted</span> <span class="n">samples</span><span class="o">.</span>
<span class="s1">'precision'</span><span class="p">,</span> <span class="s1">'predicted'</span><span class="p">,</span> <span class="n">average</span><span class="p">,</span> <span class="n">warn_for</span><span class="p">)</span></code></pre>
<p>Ça ne marche pas du tout. La raison pour laquelle ce message est affiché est que le paramètre F (<a href="https://en.wikipedia.org/wiki/F1_score">score F1</a>) est indéterminé. Ce paramètre est un estimateur de la qualité d'une classification. Il dépend de la <a href="https://fr.wikipedia.org/wiki/Pr%C3%A9cision_et_rappel">précision et du rappel</a>. Une image vaut mieux qu'un long discours, le dessin sur la page wikipedia :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f7468756d622f322f32362f507265636973696f6e726563616c6c2e7376672f33353070782d507265636973696f6e726563616c6c2e7376672e706e67/350px-Precisionrecall.svg.png" alt="précision et rappel" title="Source : https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/Precisionrecall.svg/350px-Precisionrecall.svg.png"></p>
<p><a href="https://fr.wikipedia.org/wiki/Matrice_de_confusion">La matrice de confusion</a> permet de comprendre pourquoi le score F est si mauvais : mis à part pour les trolls de qualité, je n'ai pas de vrai positif ! </p>
<p>Pour lire le graphique : la prédiction parfaite aurait 100 % sur chaque case de la diagonale. C'est le cas ici des <em>qualitaÿ trolls</em> qui sont tous bien identifiés. Mais il y a un biais vers les <em>qualitaÿ trolls</em>. L'algorithme interprète ainsi erronément 100 % des <em>average trolls</em> comme des <em>qualitaÿ trolls</em> par exemple.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f4e425f312e706e67/mat_NB_1.png" alt="mat_NB" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_NB_1.png"><br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f4e425f6e6f726d5f312e706e67/mat_NB_norm_1.png" alt="mat_NB_norm" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_NB_norm_1.png"></p>
<p>Au passage, j'affiche la matrice de confusion à l'aide du code de la <a href="http://scikit-learn.org/stable/auto_examples/model_selection/plot_confusion_matrix.html">documentation officielle</a>.</p>
<p>Mon classificateur est mauvais. Il est probablement possible d'en améliorer les performances mais j'ai préféré changer d’algorithme.</p>
<h3 id="support-vector-machine-svm">Support vector machine (SVM)</h3>
<p>D'après la <a href="http://scikit-learn.org/stable/tutorial/text_analytics/working_with_text_data.html">documentation</a> officielle, il s'agit de l’algorithme de classification de texte le plus performant pour le texte. <a href="http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDClassifier.html">SGDClassifier</a> est basé sur un classificateur linéaire et un algorithme du gradient stochastique (abréviation SGD). Je vous avoue ne pas encore maîtriser ces subtilités. Si quelqu'un à l'aise avec ces notions veut participer à la discussion, il est le bienvenu.</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.linear_model</span> <span class="kn">import</span> <span class="n">SGDClassifier</span>
<span class="n">text_clf</span> <span class="o">=</span> <span class="n">Pipeline</span><span class="p">([(</span><span class="s1">'vect'</span><span class="p">,</span> <span class="n">CountVectorizer</span><span class="p">()),</span>
<span class="p">(</span><span class="s1">'tfidf'</span><span class="p">,</span> <span class="n">TfidfTransformer</span><span class="p">()),</span>
<span class="p">(</span><span class="s1">'clf'</span><span class="p">,</span> <span class="n">SGDClassifier</span><span class="p">()),])</span>
<span class="n">_</span> <span class="o">=</span> <span class="n">text_clf</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">lf_data</span><span class="o">.</span><span class="n">content</span><span class="p">,</span> <span class="n">lf_data</span><span class="o">.</span><span class="n">quality_content</span><span class="p">)</span>
<span class="n">predicted</span> <span class="o">=</span> <span class="n">text_clf</span><span class="o">.</span><span class="n">predict</span><span class="p">(</span><span class="n">diaries_test</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
<span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">predicted</span> <span class="o">==</span> <span class="n">diaries_test</span><span class="o">.</span><span class="n">quality_content</span><span class="p">)</span>
<span class="mf">0.95</span></code></pre>
<p>Le score est très bon. Il est possible d'afficher plus d'informations à propos des prédictions :</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn</span> <span class="kn">import</span> <span class="n">metrics</span>
<span class="k">print</span><span class="p">(</span><span class="n">metrics</span><span class="o">.</span><span class="n">classification_report</span><span class="p">(</span><span class="n">diaries_test</span><span class="o">.</span><span class="n">quality_content</span><span class="p">,</span> <span class="n">predicted</span><span class="p">,</span> <span class="n">target_names</span><span class="o">=</span><span class="n">targets_names</span><span class="p">))</span>
<span class="n">precision</span> <span class="n">recall</span> <span class="n">f1</span><span class="o">-</span><span class="n">score</span> <span class="n">support</span>
<span class="n">Average</span> <span class="n">Troll</span> <span class="mf">0.99</span> <span class="mf">0.93</span> <span class="mf">0.96</span> <span class="mi">523</span>
<span class="n">Great</span> <span class="n">Troll</span> <span class="mf">1.00</span> <span class="mf">0.94</span> <span class="mf">0.97</span> <span class="mi">80</span>
<span class="n">Magnificent</span> <span class="n">Troll</span> <span class="mf">1.00</span> <span class="mf">0.94</span> <span class="mf">0.97</span> <span class="mi">72</span>
<span class="n">Quality</span> <span class="n">Troll</span> <span class="mf">0.92</span> <span class="mf">0.99</span> <span class="mf">0.96</span> <span class="mi">509</span>
<span class="n">avg</span> <span class="o">/</span> <span class="n">total</span> <span class="mf">0.96</span> <span class="mf">0.96</span> <span class="mf">0.96</span> <span class="mi">1184</span></code></pre>
<pre><code class="python"><span class="c1"># Affichage de la matrice de confusion</span>
<span class="n">metrics</span><span class="o">.</span><span class="n">confusion_matrix</span><span class="p">(</span><span class="n">diaries_test</span><span class="o">.</span><span class="n">quality_content</span><span class="p">,</span> <span class="n">predicted</span><span class="p">)</span>
<span class="c1"># Compute confusion matrix</span>
<span class="kn">import</span> <span class="nn">itertools</span>
<span class="n">cnf_matrix</span> <span class="o">=</span> <span class="n">confusion_matrix</span><span class="p">(</span><span class="n">diaries_test</span><span class="p">[</span><span class="s1">'quality_content'</span><span class="p">],</span> <span class="n">predicted</span><span class="p">)</span>
<span class="n">np</span><span class="o">.</span><span class="n">set_printoptions</span><span class="p">(</span><span class="n">precision</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="c1"># Plot non-normalized confusion matrix</span>
<span class="n">plt</span><span class="o">.</span><span class="n">figure</span><span class="p">()</span>
<span class="n">plot_confusion_matrix</span><span class="p">(</span><span class="n">cnf_matrix</span><span class="p">,</span> <span class="n">classes</span><span class="o">=</span><span class="n">targets_names</span><span class="p">,</span>
<span class="n">title</span><span class="o">=</span><span class="s1">'Confusion matrix, without normalization'</span><span class="p">)</span>
<span class="c1"># Plot normalized confusion matrix</span>
<span class="n">plt</span><span class="o">.</span><span class="n">figure</span><span class="p">()</span>
<span class="n">plot_confusion_matrix</span><span class="p">(</span><span class="n">cnf_matrix</span><span class="p">,</span> <span class="n">classes</span><span class="o">=</span><span class="n">targets_names</span><span class="p">,</span> <span class="n">normalize</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="n">title</span><span class="o">=</span><span class="s1">'Normalized confusion matrix'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
<span class="n">Confusion</span> <span class="n">matrix</span><span class="p">,</span> <span class="n">without</span> <span class="n">normalization</span>
<span class="n">Confusion</span> <span class="n">matrix</span><span class="p">,</span> <span class="n">without</span> <span class="n">normalization</span>
<span class="p">[[</span><span class="mi">489</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mi">34</span><span class="p">]</span>
<span class="p">[</span> <span class="mi">2</span> <span class="mi">75</span> <span class="mi">0</span> <span class="mi">3</span><span class="p">]</span>
<span class="p">[</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mi">68</span> <span class="mi">4</span><span class="p">]</span>
<span class="p">[</span> <span class="mi">5</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mi">504</span><span class="p">]]</span>
<span class="n">Normalized</span> <span class="n">confusion</span> <span class="n">matrix</span>
<span class="p">[[</span> <span class="mf">0.93</span> <span class="mf">0.</span> <span class="mf">0.</span> <span class="mf">0.07</span><span class="p">]</span>
<span class="p">[</span> <span class="mf">0.03</span> <span class="mf">0.94</span> <span class="mf">0.</span> <span class="mf">0.04</span><span class="p">]</span>
<span class="p">[</span> <span class="mf">0.</span> <span class="mf">0.</span> <span class="mf">0.94</span> <span class="mf">0.06</span><span class="p">]</span>
<span class="p">[</span> <span class="mf">0.01</span> <span class="mf">0.</span> <span class="mf">0.</span> <span class="mf">0.99</span><span class="p">]]</span></code></pre>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f73636f72655f3039355f312e706e67/mat_score_095_1.png" alt="matrice de confusion 0.95" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_score_095_1.png"><br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f73636f72655f3039355f6e6f726d5f312e706e67/mat_score_095_norm_1.png" alt="matrice de confusion 0.95 norm" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_score_095_norm_1.png"></p>
<h2 id="validation-croisée">Validation croisée</h2>
<p>Ces résultats sont très intéressants mais il est important de tester la solidité du modèle. Cette étape est appelée <a href="https://fr.wikipedia.org/wiki/Validation_crois%C3%A9e">validation croisée</a>. scikit-learn permet de réaliser ces tests de manière automatisée. L'idée est d’échantillonner une partie des journaux (10 % dans notre cas), d'entraîner le modèle sur les 90 % restant et de tester le modèle sur ces 10 % "caché". On affiche ensuite les scores pondérés en fonction du nombre d’occurrence de journaux dans chaque catégorie.</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">cross_val_score</span>
<span class="n">scores</span> <span class="o">=</span> <span class="n">cross_val_score</span><span class="p">(</span><span class="n">text_clf</span><span class="p">,</span> <span class="c1"># steps to convert raw messages into models</span>
<span class="n">lf_data</span><span class="o">.</span><span class="n">content</span><span class="p">,</span> <span class="c1"># training data</span>
<span class="n">lf_data</span><span class="o">.</span><span class="n">quality_content</span><span class="p">,</span> <span class="c1"># training labels</span>
<span class="n">cv</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="c1"># split data randomly into 10 parts: 9 for training, 1 for scoring</span>
<span class="n">scoring</span><span class="o">=</span><span class="s1">'accuracy'</span><span class="p">,</span> <span class="c1"># which scoring metric?</span>
<span class="n">n_jobs</span><span class="o">=-</span><span class="mi">1</span><span class="p">,</span> <span class="c1"># -1 = use all cores = faster</span>
<span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">scores</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Total diaries classified:'</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">lf_data</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Score:'</span><span class="p">,</span> <span class="nb">sum</span><span class="p">(</span><span class="n">scores</span><span class="p">)</span><span class="o">/</span><span class="nb">len</span><span class="p">(</span><span class="n">scores</span><span class="p">))</span>
<span class="p">[</span> <span class="mf">0.54</span> <span class="mf">0.53</span> <span class="mf">0.55</span> <span class="mf">0.55</span> <span class="mf">0.56</span> <span class="mf">0.57</span> <span class="mf">0.54</span> <span class="mf">0.52</span> <span class="mf">0.56</span> <span class="mf">0.56</span><span class="p">]</span>
<span class="n">Total</span> <span class="n">diaries</span> <span class="n">classified</span><span class="p">:</span> <span class="mi">5921</span>
<span class="n">Score</span><span class="p">:</span> <span class="mf">0.548226957256</span></code></pre>
<p>Le score est égal à 0.55. Ce n'est pas terrible. Si on préfère afficher la matrice de confusion, il faut utiliser les Kfold qui reposent sur le même principe que <code>cross_val_score</code> et implémenter une boucle.</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">KFold</span>
<span class="kn">from</span> <span class="nn">sklearn.metrics</span> <span class="kn">import</span> <span class="n">confusion_matrix</span><span class="p">,</span> <span class="n">f1_score</span><span class="p">,</span><span class="n">precision_score</span>
<span class="n">k_fold</span> <span class="o">=</span> <span class="n">KFold</span><span class="p">(</span><span class="n">n_splits</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
<span class="n">scores</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">confusion</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">([[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">]])</span>
<span class="k">for</span> <span class="n">train_indices</span><span class="p">,</span> <span class="n">test_indices</span> <span class="ow">in</span> <span class="n">k_fold</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">lf_data</span><span class="p">):</span>
<span class="n">train_text</span> <span class="o">=</span> <span class="n">lf_data</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">train_indices</span><span class="p">][</span><span class="s1">'content'</span><span class="p">]</span><span class="o">.</span><span class="n">values</span>
<span class="n">train_y</span> <span class="o">=</span> <span class="n">lf_data</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">train_indices</span><span class="p">][</span><span class="s1">'quality_content'</span><span class="p">]</span><span class="o">.</span><span class="n">values</span>
<span class="n">test_text</span> <span class="o">=</span> <span class="n">lf_data</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">test_indices</span><span class="p">][</span><span class="s1">'content'</span><span class="p">]</span><span class="o">.</span><span class="n">values</span>
<span class="n">test_y</span> <span class="o">=</span> <span class="n">lf_data</span><span class="o">.</span><span class="n">iloc</span><span class="p">[</span><span class="n">test_indices</span><span class="p">][</span><span class="s1">'quality_content'</span><span class="p">]</span><span class="o">.</span><span class="n">values</span>
<span class="n">text_clf</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_text</span><span class="p">,</span> <span class="n">train_y</span><span class="p">)</span>
<span class="n">predictions</span> <span class="o">=</span> <span class="n">text_clf</span><span class="o">.</span><span class="n">predict</span><span class="p">(</span><span class="n">test_text</span><span class="p">)</span>
<span class="n">confusion</span> <span class="o">+=</span> <span class="n">confusion_matrix</span><span class="p">(</span><span class="n">test_y</span><span class="p">,</span> <span class="n">predictions</span><span class="p">)</span>
<span class="n">score</span> <span class="o">=</span> <span class="n">f1_score</span><span class="p">(</span><span class="n">test_y</span><span class="p">,</span> <span class="n">predictions</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="s1">'weighted'</span><span class="p">)</span>
<span class="n">ps</span> <span class="o">=</span> <span class="n">precision_score</span><span class="p">(</span><span class="n">test_y</span><span class="p">,</span> <span class="n">predictions</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="s1">'weighted'</span><span class="p">)</span>
<span class="n">scores</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">score</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Total diaries classified:'</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">lf_data</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Score:'</span><span class="p">,</span> <span class="nb">sum</span><span class="p">(</span><span class="n">scores</span><span class="p">)</span><span class="o">/</span><span class="nb">len</span><span class="p">(</span><span class="n">scores</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Confusion matrix:'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">confusion</span><span class="p">)</span>
<span class="n">Total</span> <span class="n">diaries</span> <span class="n">classified</span><span class="p">:</span> <span class="mi">5921</span>
<span class="n">Score</span><span class="p">:</span> <span class="mf">0.519244446873</span>
<span class="n">Confusion</span> <span class="n">matrix</span><span class="p">:</span>
<span class="p">[[</span><span class="mi">1475</span> <span class="mi">22</span> <span class="mi">13</span> <span class="mi">1104</span><span class="p">]</span>
<span class="p">[</span> <span class="mi">253</span> <span class="mi">11</span> <span class="mi">16</span> <span class="mi">180</span><span class="p">]</span>
<span class="p">[</span> <span class="mi">164</span> <span class="mi">15</span> <span class="mi">26</span> <span class="mi">97</span><span class="p">]</span>
<span class="p">[</span> <span class="mi">794</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">1736</span><span class="p">]]</span>
<span class="n">scores</span>
<span class="p">[</span><span class="mf">0.48812704076867125</span><span class="p">,</span>
<span class="mf">0.50096444244611738</span><span class="p">,</span>
<span class="mf">0.53296513209879548</span><span class="p">,</span>
<span class="mf">0.50865953156976373</span><span class="p">,</span>
<span class="mf">0.53358760110311787</span><span class="p">,</span>
<span class="mf">0.52464153844229733</span><span class="p">,</span>
<span class="mf">0.53897239391380014</span><span class="p">,</span>
<span class="mf">0.5090212038928732</span><span class="p">,</span>
<span class="mf">0.5340084448235829</span><span class="p">,</span>
<span class="mf">0.5214971396677468</span><span class="p">]</span></code></pre>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f63726f73735f312e706e67/mat_cross_1.png" alt="validation_croisée" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_cross_1.png"><br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f63726f73735f6e6f726d5f312e706e67/mat_cross_norm_1.png" alt="validation_croisée norm" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_cross_norm_1.png"></p>
<p>Comme on le voit, les résultats sont très mauvais. Environ 44 % des journaux "Average Troll" sont attribués à la classe "Quality Troll" ! <strong>Si les auteurs suivent la même logique que cet algorithme</strong>, ils ont tendance à sur-estimer fortement leurs écrits. De même, 30 % des "Quality Troll" sont attribués à la classe "Average Troll". <strong>En suivant cette logique</strong>, les auteurs de contenu de qualité auraient tendance à se sous-estimer. Par ailleurs, il faut noter que ces classes sont voisines : score de 0 à 20 et de 20 à l'infini (et au delà). </p>
<p>Plus inquiétant : les contenus avec un score négatif sont attribués majoritairement aux classes à score positif. <strong>Un auteur de contenu moinsé qui penserait comme la machine serait persuadé que son texte est de qualité.</strong> Il ne comprendrait pas le score négatif qui en résulte.</p>
<h2 id="optimisation-des-paramètres">Optimisation des paramètres</h2>
<p>Et si nos mauvais résultats étaient dus au choix d'un mauvais jeu de paramètres de départ ? Le <em>pipeline</em> choisi dépend de nombreux paramètres ajustables. Scikit-learn permet d'optimiser ces paramètres facilement afin de trouver le meilleur compromis.</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.linear_model</span> <span class="kn">import</span> <span class="n">SGDClassifier</span>
<span class="n">text_clf</span> <span class="o">=</span> <span class="n">Pipeline</span><span class="p">([(</span><span class="s1">'vect'</span><span class="p">,</span> <span class="n">CountVectorizer</span><span class="p">()),</span>
<span class="p">(</span><span class="s1">'tfidf'</span><span class="p">,</span> <span class="n">TfidfTransformer</span><span class="p">()),</span>
<span class="p">(</span><span class="s1">'clf'</span><span class="p">,</span> <span class="n">SGDClassifier</span><span class="p">()),])</span></code></pre>
<p>Les paramètres ajustables sont précédés du nom de l'étape correspondante. Les explications concernant ces paramètres sont disponibles dans la documentation officielle :</p>
<ul>
<li>
<a href="http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html">CountVectorizer</a> ;</li>
<li>
<a href="http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfTransformer.html">TfidfTransformer</a> ;</li>
<li>
<a href="http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDClassifier.html">SGDClassifier</a>.</li>
</ul><pre><code class="python"><span class="nb">sorted</span><span class="p">(</span><span class="n">text_clf</span><span class="o">.</span><span class="n">get_params</span><span class="p">()</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
<span class="p">[</span><span class="s1">'clf'</span><span class="p">,</span>
<span class="s1">'clf__alpha'</span><span class="p">,</span>
<span class="s1">'clf__average'</span><span class="p">,</span>
<span class="s1">'clf__class_weight'</span><span class="p">,</span>
<span class="s1">'clf__epsilon'</span><span class="p">,</span>
<span class="s1">'clf__eta0'</span><span class="p">,</span>
<span class="s1">'clf__fit_intercept'</span><span class="p">,</span>
<span class="s1">'clf__l1_ratio'</span><span class="p">,</span>
<span class="s1">'clf__learning_rate'</span><span class="p">,</span>
<span class="s1">'clf__loss'</span><span class="p">,</span>
<span class="s1">'clf__n_iter'</span><span class="p">,</span>
<span class="s1">'clf__n_jobs'</span><span class="p">,</span>
<span class="s1">'clf__penalty'</span><span class="p">,</span>
<span class="s1">'clf__power_t'</span><span class="p">,</span>
<span class="s1">'clf__random_state'</span><span class="p">,</span>
<span class="s1">'clf__shuffle'</span><span class="p">,</span>
<span class="s1">'clf__verbose'</span><span class="p">,</span>
<span class="s1">'clf__warm_start'</span><span class="p">,</span>
<span class="s1">'steps'</span><span class="p">,</span>
<span class="s1">'tfidf'</span><span class="p">,</span>
<span class="s1">'tfidf__norm'</span><span class="p">,</span>
<span class="s1">'tfidf__smooth_idf'</span><span class="p">,</span>
<span class="s1">'tfidf__sublinear_tf'</span><span class="p">,</span>
<span class="s1">'tfidf__use_idf'</span><span class="p">,</span>
<span class="s1">'vect'</span><span class="p">,</span>
<span class="s1">'vect__analyzer'</span><span class="p">,</span>
<span class="s1">'vect__binary'</span><span class="p">,</span>
<span class="s1">'vect__decode_error'</span><span class="p">,</span>
<span class="s1">'vect__dtype'</span><span class="p">,</span>
<span class="s1">'vect__encoding'</span><span class="p">,</span>
<span class="s1">'vect__input'</span><span class="p">,</span>
<span class="s1">'vect__lowercase'</span><span class="p">,</span>
<span class="s1">'vect__max_df'</span><span class="p">,</span>
<span class="s1">'vect__max_features'</span><span class="p">,</span>
<span class="s1">'vect__min_df'</span><span class="p">,</span>
<span class="s1">'vect__ngram_range'</span><span class="p">,</span>
<span class="s1">'vect__preprocessor'</span><span class="p">,</span>
<span class="s1">'vect__stop_words'</span><span class="p">,</span>
<span class="s1">'vect__strip_accents'</span><span class="p">,</span>
<span class="s1">'vect__token_pattern'</span><span class="p">,</span>
<span class="s1">'vect__tokenizer'</span><span class="p">,</span>
<span class="s1">'vect__vocabulary'</span><span class="p">]</span></code></pre>
<p>Le code ci-dessous permet d'ajuster les paramètres suivants :</p>
<ul>
<li>
<code>tfidf__use_idf</code> : utilisation ou non de la pondération par la fréquence des mots ;</li>
<li> <code>clf__loss</code> : le type de fonction qui "caractérise" la perte ou encore le type de transition entre les catégories. Cette transition peut être abrupte ou plus ou moins lissée. (<a href="http://www.cmap.polytechnique.fr/%7Egiraud/MAP553/sec3.4-3.6correction.pdf">illustration du phénomène (P 20)</a>) ;</li>
<li> <code>clf__alpha</code> <a href="http://scikit-learn.org/stable/modules/svm.html#svc">un paramètre mathématique strictement positif</a>.</li>
</ul><p>Évidemment, le temps de calcul dépend du nombre de paramètres à ajuster. Les autres paramètres sont laissés à leur valeur par défaut.</p>
<pre><code class="python"><span class="n">params</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'tfidf__use_idf'</span><span class="p">:</span> <span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="bp">False</span><span class="p">),</span>
<span class="s1">'clf__loss'</span><span class="p">:(</span><span class="s1">'huber'</span><span class="p">,</span> <span class="s1">'modified_huber'</span><span class="p">,</span> <span class="s1">'epsilon_insensitive'</span><span class="p">,</span> <span class="s1">'hinge'</span><span class="p">,</span> <span class="s1">'log'</span><span class="p">),</span>
<span class="s1">'clf__alpha'</span><span class="p">:(</span><span class="mi">1</span><span class="p">,</span><span class="mf">0.001</span><span class="p">,</span> <span class="mf">0.00001</span><span class="p">),}</span>
<span class="n">gs_clf</span> <span class="o">=</span> <span class="n">GridSearchCV</span><span class="p">(</span><span class="n">text_clf</span><span class="p">,</span> <span class="n">params</span><span class="p">,</span> <span class="n">n_jobs</span><span class="o">=-</span><span class="mi">1</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">refit</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span><span class="n">scoring</span><span class="o">=</span><span class="s1">'accuracy'</span><span class="p">,)</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"Performing grid search..."</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"pipeline:"</span><span class="p">,</span> <span class="p">[</span><span class="n">name</span> <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">text_clf</span><span class="o">.</span><span class="n">steps</span><span class="p">])</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"parameters:"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">params</span><span class="p">)</span>
<span class="n">t0</span> <span class="o">=</span> <span class="n">time</span><span class="p">()</span>
<span class="n">gs_clf</span> <span class="o">=</span> <span class="n">gs_clf</span><span class="o">.</span><span class="n">fit</span><span class="p">(</span><span class="n">lf_data</span><span class="o">.</span><span class="n">content</span><span class="p">,</span> <span class="n">targets</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"done in </span><span class="si">%0.3f</span><span class="s2">s"</span> <span class="o">%</span> <span class="p">(</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">t0</span><span class="p">))</span>
<span class="k">print</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"Best score: </span><span class="si">%0.3f</span><span class="s2">"</span> <span class="o">%</span> <span class="n">gs_clf</span><span class="o">.</span><span class="n">best_score_</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"Best parameters set:"</span><span class="p">)</span>
<span class="n">best_parameters</span> <span class="o">=</span> <span class="n">gs_clf</span><span class="o">.</span><span class="n">best_estimator_</span><span class="o">.</span><span class="n">get_params</span><span class="p">()</span>
<span class="k">for</span> <span class="n">param_name</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">params</span><span class="o">.</span><span class="n">keys</span><span class="p">()):</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\t</span><span class="si">%s</span><span class="s2">: </span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">param_name</span><span class="p">,</span> <span class="n">best_parameters</span><span class="p">[</span><span class="n">param_name</span><span class="p">]))</span></code></pre>
<p>Ce qui donne :</p>
<pre><code class="python"> <span class="n">Performing</span> <span class="n">grid</span> <span class="n">search</span><span class="o">...</span>
<span class="n">pipeline</span><span class="p">:</span> <span class="p">[</span><span class="s1">'vect'</span><span class="p">,</span> <span class="s1">'tfidf'</span><span class="p">,</span> <span class="s1">'clf'</span><span class="p">]</span>
<span class="n">parameters</span><span class="p">:</span>
<span class="p">{</span><span class="s1">'tfidf__use_idf'</span><span class="p">:</span> <span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="bp">False</span><span class="p">),</span> <span class="s1">'clf__loss'</span><span class="p">:</span> <span class="p">(</span><span class="s1">'huber'</span><span class="p">,</span> <span class="s1">'modified_huber'</span><span class="p">,</span> <span class="s1">'epsilon_insensitive'</span><span class="p">,</span> <span class="s1">'hinge'</span><span class="p">,</span> <span class="s1">'log'</span><span class="p">),</span> <span class="s1">'clf__alpha'</span><span class="p">:</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mf">0.001</span><span class="p">,</span> <span class="mf">1e-05</span><span class="p">)}</span>
<span class="n">done</span> <span class="ow">in</span> <span class="mf">108.027</span><span class="n">s</span>
<span class="n">Best</span> <span class="n">score</span><span class="p">:</span> <span class="mf">0.547</span>
<span class="n">Best</span> <span class="n">parameters</span> <span class="nb">set</span><span class="p">:</span>
<span class="n">clf__alpha</span><span class="p">:</span> <span class="mf">0.001</span>
<span class="n">clf__loss</span><span class="p">:</span> <span class="s1">'modified_huber'</span>
<span class="n">tfidf__use_idf</span><span class="p">:</span> <span class="bp">True</span></code></pre>
<p>Malheureusement, le score semble encore assez bas. Par ailleurs, le meilleur estimateur est également disponible pour utilisation future :</p>
<pre><code class="python"><span class="n">gs_clf</span><span class="o">.</span><span class="n">best_estimator_</span>
<span class="n">Pipeline</span><span class="p">(</span><span class="n">steps</span><span class="o">=</span><span class="p">[(</span><span class="s1">'vect'</span><span class="p">,</span> <span class="n">CountVectorizer</span><span class="p">(</span><span class="n">analyzer</span><span class="o">=</span><span class="s1">'word'</span><span class="p">,</span> <span class="n">binary</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">decode_error</span><span class="o">=</span><span class="s1">'strict'</span><span class="p">,</span>
<span class="n">dtype</span><span class="o">=<</span><span class="k">class</span> <span class="err">'</span><span class="nc">numpy</span><span class="o">.</span><span class="n">int64</span><span class="s1">'>, encoding='</span><span class="n">utf</span><span class="o">-</span><span class="mi">8</span><span class="s1">', input='</span><span class="n">content</span><span class="s1">',</span>
<span class="n">lowercase</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">max_df</span><span class="o">=</span><span class="mf">1.0</span><span class="p">,</span> <span class="n">max_features</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">min_df</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
<span class="n">ngram_range</span><span class="o">=</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="n">preprocessor</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">stop_words</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span>
<span class="n">strip</span><span class="o">...</span> <span class="n">penalty</span><span class="o">=</span><span class="s1">'l2'</span><span class="p">,</span> <span class="n">power_t</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">random_state</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">shuffle</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="n">verbose</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">warm_start</span><span class="o">=</span><span class="bp">False</span><span class="p">))])</span></code></pre>
<h3 id="test-sur-un-échantillon-de-données-connues">Test sur un échantillon de données connues</h3>
<p>Comme précédemment, il est possible de tester le modèle sur un échantillon de données connues. L'ajustement a été réalisé avec le meilleur jeu de paramètres grâce à l'option <code>refit=True</code> passée à <code>GridSearchCV</code>. Les résultats du score F1 sont encore une fois très bons mais l'amélioration du score est nulle : il plafonne entre 0.95 et 0.96.</p>
<pre><code class="python"><span class="k">print</span><span class="p">(</span><span class="n">metrics</span><span class="o">.</span><span class="n">classification_report</span><span class="p">(</span><span class="n">diaries_test</span><span class="o">.</span><span class="n">quality_content</span><span class="p">,</span> <span class="n">predicted</span><span class="p">,</span> <span class="n">target_names</span><span class="o">=</span><span class="n">targets_names</span><span class="p">))</span>
<span class="n">precision</span> <span class="n">recall</span> <span class="n">f1</span><span class="o">-</span><span class="n">score</span> <span class="n">support</span>
<span class="n">Average</span> <span class="n">Troll</span> <span class="mf">0.99</span> <span class="mf">0.93</span> <span class="mf">0.96</span> <span class="mi">523</span>
<span class="n">Great</span> <span class="n">Troll</span> <span class="mf">1.00</span> <span class="mf">0.94</span> <span class="mf">0.97</span> <span class="mi">80</span>
<span class="n">Magnificent</span> <span class="n">Troll</span> <span class="mf">1.00</span> <span class="mf">0.94</span> <span class="mf">0.97</span> <span class="mi">72</span>
<span class="n">Quality</span> <span class="n">Troll</span> <span class="mf">0.92</span> <span class="mf">0.99</span> <span class="mf">0.96</span> <span class="mi">509</span>
<span class="n">avg</span> <span class="o">/</span> <span class="n">total</span> <span class="mf">0.96</span> <span class="mf">0.96</span> <span class="mf">0.96</span> <span class="mi">1184</span></code></pre>
<p>De même, la matrice de confusion est excellente :<br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f677269645f312e706e67/mat_grid_1.png" alt="mat_grid" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_grid_1.png"><br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f677269645f6e6f726d5f312e706e67/mat_grid_norm_1.png" alt="mat_grid_norm" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_grid_norm_1.png"></p>
<h3 id="test-sur-un-échantillon-de-données-inconnues">Test sur un échantillon de données inconnues</h3>
<p>Pour aller plus loin, j'ai testé le modèle sur de nouvelles données (des journaux plus anciens). Ces données ne font pas partie de mes journaux de base. En principe, le résultat sera similaire à ce qu'on obtient par validation croisée mais cette technique a pour avantage d'augmenter la taille de la base de journaux disponibles. Une autre possibilité consiste à relancer la validation croisée après avoir fusionné ces nouvelles données aux anciennes.</p>
<pre><code class="python"><span class="n">filename</span> <span class="o">=</span> <span class="sa">r</span><span class="s1">'out_of_sample.csv'</span>
<span class="n">lf_out</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"UTF-8"</span><span class="p">,</span> <span class="n">sep</span><span class="o">=</span><span class="s1">'£'</span><span class="p">,</span> <span class="n">engine</span><span class="o">=</span><span class="s1">'python'</span><span class="p">,</span> <span class="n">quotechar</span><span class="o">=</span><span class="s1">'µ'</span><span class="p">)</span>
<span class="n">lf_out</span> <span class="o">=</span> <span class="n">lf_out</span><span class="o">.</span><span class="n">reindex</span><span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">permutation</span><span class="p">(</span><span class="n">lf_out</span><span class="o">.</span><span class="n">index</span><span class="p">))</span>
<span class="n">lf_out</span><span class="o">.</span><span class="n">quality_content</span><span class="o">.</span><span class="n">value_counts</span><span class="p">()</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">kind</span><span class="o">=</span><span class="s1">'bar'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s1">'Occurences'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">yticks</span><span class="p">(</span><span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s1">'Trolls'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">xticks</span><span class="p">(</span><span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span></code></pre>
<p>Ces nouvelles données sont similaires aux journaux déjà disponibles.<br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6f75745f6f665f73616d706c655f312e706e67/out_of_sample_1.png" alt="out_of_sample" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/out_of_sample_1.png"></p>
<pre><code class="python"><span class="n">predicted_out</span> <span class="o">=</span> <span class="n">text_clf</span><span class="o">.</span><span class="n">predict</span><span class="p">(</span><span class="n">lf_out</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
<span class="n">np</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">predicted_out</span> <span class="o">==</span> <span class="n">lf_out</span><span class="o">.</span><span class="n">quality_content</span><span class="p">)</span>
<span class="n">score_out</span> <span class="o">=</span> <span class="n">f1_score</span><span class="p">(</span><span class="n">lf_out</span><span class="p">[</span><span class="s1">'quality_content'</span><span class="p">],</span> <span class="n">predicted_out</span><span class="p">,</span> <span class="n">average</span><span class="o">=</span><span class="s1">'weighted'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Diaries:'</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">lf_out</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Score:'</span><span class="p">,</span> <span class="n">score_out</span><span class="p">)</span>
<span class="n">cnf_matrix_out</span> <span class="o">=</span> <span class="n">confusion_matrix</span><span class="p">(</span><span class="n">lf_out</span><span class="p">[</span><span class="s1">'quality_content'</span><span class="p">],</span> <span class="n">predicted_out</span><span class="p">)</span>
<span class="n">np</span><span class="o">.</span><span class="n">set_printoptions</span><span class="p">(</span><span class="n">precision</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="c1"># Plot non-normalized confusion matrix</span>
<span class="n">plt</span><span class="o">.</span><span class="n">figure</span><span class="p">()</span>
<span class="n">plot_confusion_matrix</span><span class="p">(</span><span class="n">cnf_matrix_out</span><span class="p">,</span> <span class="n">classes</span><span class="o">=</span><span class="n">targets_names</span><span class="p">,</span>
<span class="n">title</span><span class="o">=</span><span class="s1">'Confusion matrix, without normalization'</span><span class="p">)</span>
<span class="c1"># Plot normalized confusion matrix</span>
<span class="n">plt</span><span class="o">.</span><span class="n">figure</span><span class="p">()</span>
<span class="n">plot_confusion_matrix</span><span class="p">(</span><span class="n">cnf_matrix_out</span><span class="p">,</span> <span class="n">classes</span><span class="o">=</span><span class="n">targets_names</span><span class="p">,</span> <span class="n">normalize</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="n">title</span><span class="o">=</span><span class="s1">'Normalized confusion matrix'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="n">metrics</span><span class="o">.</span><span class="n">classification_report</span><span class="p">(</span><span class="n">lf_out</span><span class="o">.</span><span class="n">quality_content</span><span class="p">,</span> <span class="n">predicted_out</span><span class="p">,</span> <span class="n">target_names</span><span class="o">=</span><span class="n">targets_names</span><span class="p">))</span>
<span class="n">Diaries</span><span class="p">:</span> <span class="mi">1500</span>
<span class="n">Score</span><span class="p">:</span> <span class="mf">0.444809984556</span>
<span class="n">Confusion</span> <span class="n">matrix</span><span class="p">,</span> <span class="n">without</span> <span class="n">normalization</span>
<span class="p">[[</span><span class="mi">452</span> <span class="mi">9</span> <span class="mi">10</span> <span class="mi">457</span><span class="p">]</span>
<span class="p">[</span> <span class="mi">90</span> <span class="mi">4</span> <span class="mi">0</span> <span class="mi">52</span><span class="p">]</span>
<span class="p">[</span> <span class="mi">42</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">26</span><span class="p">]</span>
<span class="p">[</span><span class="mi">126</span> <span class="mi">2</span> <span class="mi">0</span> <span class="mi">219</span><span class="p">]]</span>
<span class="n">Normalized</span> <span class="n">confusion</span> <span class="n">matrix</span>
<span class="p">[[</span> <span class="mf">0.49</span> <span class="mf">0.01</span> <span class="mf">0.01</span> <span class="mf">0.49</span><span class="p">]</span>
<span class="p">[</span> <span class="mf">0.62</span> <span class="mf">0.03</span> <span class="mf">0.</span> <span class="mf">0.36</span><span class="p">]</span>
<span class="p">[</span> <span class="mf">0.53</span> <span class="mf">0.06</span> <span class="mf">0.08</span> <span class="mf">0.33</span><span class="p">]</span>
<span class="p">[</span> <span class="mf">0.36</span> <span class="mf">0.01</span> <span class="mf">0.</span> <span class="mf">0.63</span><span class="p">]]</span></code></pre>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f6f75745f312e706e67/mat_out_1.png" alt="mat_out" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_out_1.png"><br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f6f75745f6e6f726d5f312e706e67/mat_out_norm_1.png" alt="mat_out_norm" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_out_norm_1.png"></p>
<pre><code class="python"> <span class="n">precision</span> <span class="n">recall</span> <span class="n">f1</span><span class="o">-</span><span class="n">score</span> <span class="n">support</span>
<span class="n">Average</span> <span class="n">Troll</span> <span class="mf">0.64</span> <span class="mf">0.49</span> <span class="mf">0.55</span> <span class="mi">928</span>
<span class="n">Great</span> <span class="n">Troll</span> <span class="mf">0.20</span> <span class="mf">0.03</span> <span class="mf">0.05</span> <span class="mi">146</span>
<span class="n">Magnificent</span> <span class="n">Troll</span> <span class="mf">0.38</span> <span class="mf">0.08</span> <span class="mf">0.13</span> <span class="mi">79</span>
<span class="n">Quality</span> <span class="n">Troll</span> <span class="mf">0.29</span> <span class="mf">0.63</span> <span class="mf">0.40</span> <span class="mi">347</span>
<span class="n">avg</span> <span class="o">/</span> <span class="n">total</span> <span class="mf">0.50</span> <span class="mf">0.45</span> <span class="mf">0.44</span> <span class="mi">1500</span></code></pre>
<pre><code>
</code></pre>
<p>Malheureusement, le résultat n'est pas bon. Encore une fois, le modèle ne peut pas s'adapter à des données inconnues. Il s'agit d'un cas assez probant de <a href="https://fr.wikipedia.org/wiki/Surapprentissage">surapprentissage</a>. L'image suivante illustre bien le problème. En cherchant à classer correctement les éléments dans la bonne catégorie, le modèle se contorsionne et ne tient pas compte de la tendance "globale".</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f312f31392f4f76657266697474696e672e737667/Overfitting.svg" alt="overfitting" title="Source : https://upload.wikimedia.org/wikipedia/commons/1/19/Overfitting.svg"></p>
<h2 id="utiliser-des-propriétés-multiples">Utiliser des propriétés multiples</h2>
<p>Bien qu'elle soit informative, l'analyse ne permet pas de prédire la catégorie avec un score supérieur à 0,5. Pour l'instant, le classificateur se comporte comme un mauvais élève pressé d'aller jouer un match de tennis après son examen <a href="https://fr.wikipedia.org/wiki/Questionnaire_%C3%A0_choix_multiples">Q.C.M.</a> : il répond la même chose (la réponse D) à toutes les questions en se disant qu'il obtiendra bien la moitié. Évidemment, cela ne fonctionne pas. L'approche "bag of words" seule ne suffit pas pour classer des journaux. Les bons journaux ne sont pas tous techniques, de même que les mauvais ne sont pas tous "politiques" (quoiqu'un journal sur l'avortement part en général très mal). Le sujet d'un journal n'est pas corrélé avec sa note finale. D'autres indicateurs doivent être pris en compte : ancienneté du compte au moment de la soumission, taille du texte (les journaux trop courts sont parfois descendu, tout comme les 'journaux fleuve parfois hallucinés' dixit <a href="//linuxfr.org/users/oumph">oumph</a>). scikit-learn permet de combiner plusieurs propriétés (appelées "features"), de déterminer celles qui ont le plus gros impact sur les résultats et d'ajuster un modèle en tenant compte des propriétés sélectionnées.</p>
<h3 id="extraction-et-préparation-des-données">Extraction et préparation des données</h3>
<p>L'analyse suivante repose sur l'utilisation des données présentes dans le fichier <code>linuxfr_complete.csv</code>. Elle correspond au notebook <a href="https://gitlab.com/jnanar/scikit-linuxfr/blob/master/diaries_classification_2.ipynb">diaries_classification_2.ipynb</a>. En plus des données présentes dans le fichier <code>linuxfr.csv</code>, ce document comporte les champs suivant :</p>
<ul>
<li>la date de création du journal ;</li>
<li>la date de création du compte ;</li>
<li>les scores précédents de l'auteur (première page des anciennes publications) ;</li>
<li>la longueur du document.</li>
</ul><pre><code class="python"><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="kn">as</span> <span class="nn">plt</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="kn">as</span> <span class="nn">pd</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="kn">as</span> <span class="nn">np</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">matplotlib</span>
<span class="c1"># Enable inline plotting</span>
<span class="o">%</span><span class="n">matplotlib</span> <span class="n">inline</span>
<span class="n">filename</span> <span class="o">=</span> <span class="sa">r</span><span class="s1">'linuxfr_complete.csv'</span>
<span class="n">lf_data</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">read_csv</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"UTF-8"</span><span class="p">,</span> <span class="n">sep</span><span class="o">=</span><span class="s1">'£'</span><span class="p">,</span> <span class="n">engine</span><span class="o">=</span><span class="s1">'python'</span><span class="p">,</span> <span class="n">quotechar</span><span class="o">=</span><span class="s1">'µ'</span><span class="p">)</span></code></pre>
<h3 id="conversion-des-dates">Conversion des dates</h3>
<p>Panda permet très facilement de convertir une chaîne de caractère correspondant à une date au format <code>datetime</code>.</p>
<pre><code class="python"><span class="n">lf_data</span><span class="p">[</span><span class="s1">'birthday'</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">to_datetime</span><span class="p">(</span><span class="n">lf_data</span><span class="p">[</span><span class="s1">'birthday'</span><span class="p">])</span>
<span class="n">lf_data</span><span class="p">[</span><span class="s1">'birthday'</span><span class="p">]</span><span class="o">.</span><span class="n">head</span><span class="p">()</span>
<span class="mi">0</span> <span class="mi">2004</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">28</span>
<span class="mi">1</span> <span class="mi">2003</span><span class="o">-</span><span class="mo">04</span><span class="o">-</span><span class="mi">22</span>
<span class="mi">2</span> <span class="mi">2004</span><span class="o">-</span><span class="mo">02</span><span class="o">-</span><span class="mi">14</span>
<span class="mi">3</span> <span class="mi">2012</span><span class="o">-</span><span class="mi">10</span><span class="o">-</span><span class="mi">22</span>
<span class="mi">4</span> <span class="mi">2009</span><span class="o">-</span><span class="mi">10</span><span class="o">-</span><span class="mo">05</span>
<span class="n">Name</span><span class="p">:</span> <span class="n">birthday</span><span class="p">,</span> <span class="n">dtype</span><span class="p">:</span> <span class="n">datetime64</span><span class="p">[</span><span class="n">ns</span><span class="p">]</span></code></pre>
<pre><code class="python"><span class="n">lf_data</span><span class="p">[</span><span class="s1">'datetime'</span><span class="p">]</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">to_datetime</span><span class="p">(</span><span class="n">lf_data</span><span class="p">[</span><span class="s1">'datetime'</span><span class="p">])</span>
<span class="n">lf_data</span><span class="p">[</span><span class="s1">'datetime'</span><span class="p">]</span><span class="o">.</span><span class="n">head</span><span class="p">()</span>
<span class="mi">0</span> <span class="mi">2017</span><span class="o">-</span><span class="mo">05</span><span class="o">-</span><span class="mi">28</span> <span class="mi">12</span><span class="p">:</span><span class="mi">59</span><span class="p">:</span><span class="mi">46</span>
<span class="mi">1</span> <span class="mi">2017</span><span class="o">-</span><span class="mo">05</span><span class="o">-</span><span class="mi">28</span> <span class="mi">09</span><span class="p">:</span><span class="mi">57</span><span class="p">:</span><span class="mo">04</span>
<span class="mi">2</span> <span class="mi">2017</span><span class="o">-</span><span class="mo">05</span><span class="o">-</span><span class="mi">28</span> <span class="mi">08</span><span class="p">:</span><span class="mi">24</span><span class="p">:</span><span class="mi">57</span>
<span class="mi">3</span> <span class="mi">2017</span><span class="o">-</span><span class="mo">05</span><span class="o">-</span><span class="mi">27</span> <span class="mi">14</span><span class="p">:</span><span class="mi">18</span><span class="p">:</span><span class="mi">10</span>
<span class="mi">4</span> <span class="mi">2017</span><span class="o">-</span><span class="mo">05</span><span class="o">-</span><span class="mi">26</span> <span class="mi">20</span><span class="p">:</span><span class="mi">12</span><span class="p">:</span><span class="mi">47</span>
<span class="n">Name</span><span class="p">:</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">dtype</span><span class="p">:</span> <span class="n">datetime64</span><span class="p">[</span><span class="n">ns</span><span class="p">]</span></code></pre>
<h3 id="Évolution-du-score-des-journaux-au-fil-du-temps">Évolution du score des journaux au fil du temps</h3>
<pre><code class="python"><span class="n">score_df</span> <span class="o">=</span> <span class="n">lf_data</span><span class="p">[[</span><span class="s1">'datetime'</span><span class="p">,</span> <span class="s1">'score'</span><span class="p">]]</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
<span class="n">score_df</span><span class="o">.</span><span class="n">index</span> <span class="o">=</span> <span class="n">score_df</span><span class="p">[</span><span class="s1">'datetime'</span><span class="p">]</span>
<span class="k">del</span> <span class="n">score_df</span><span class="p">[</span><span class="s1">'datetime'</span><span class="p">]</span></code></pre>
<p>L'évolution des scores au fil du temps est alors facilement affichable. Je trouve personnellement qu'on n'a pas trop à se plaindre : la qualité générale des journaux est plutôt bonne. </p>
<pre><code class="python"><span class="n">score_df</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">marker</span><span class="o">=</span><span class="s1">'o'</span><span class="p">,</span> <span class="n">grid</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span><span class="mi">9</span><span class="p">))</span>
<span class="n">plt</span><span class="o">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s1">'Score'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">yticks</span><span class="p">(</span><span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s1">'Date'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">xticks</span><span class="p">(</span><span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span></code></pre>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6461746574696d655f322e706e67/datetime_2.png" alt="Date time linuxfr_complete.csv" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/datetime_2.png"></p>
<h3 id="calcul-de-lâge-dun-compte">Calcul de l’âge d’un compte</h3>
<p>L'âge d'un compte peut facilement être calculé en soustrayant la date de création du compte à la date de création du journal. Pour une raison inconnue, cet âge est parfois négatif. Le code suivant tient compte de ce souci. Un compte qui a moins d'un jour se voit affublé de la propriété "Newbie".</p>
<pre><code class="python"><span class="n">lf_data</span><span class="p">[</span><span class="s1">'age'</span><span class="p">]</span> <span class="o">=</span> <span class="n">lf_data</span><span class="p">[</span><span class="s1">'datetime'</span><span class="p">]</span><span class="o">-</span><span class="n">lf_data</span><span class="p">[</span><span class="s1">'birthday'</span><span class="p">]</span>
<span class="n">lf_data</span><span class="p">[</span><span class="s1">'newbie'</span><span class="p">]</span> <span class="o">=</span> <span class="bp">False</span></code></pre>
<pre><code class="python"><span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">lf_data</span><span class="o">.</span><span class="n">iterrows</span><span class="p">():</span>
<span class="c1"># Problem: sometimes, age << 0</span>
<span class="k">if</span> <span class="n">line</span><span class="p">[</span><span class="s1">'age'</span><span class="p">]</span> <span class="o"><</span> <span class="n">pd</span><span class="o">.</span><span class="n">Timedelta</span><span class="p">(</span><span class="s2">"0 day"</span><span class="p">):</span>
<span class="n">line</span><span class="p">[</span><span class="s1">'age'</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span> <span class="n">line</span><span class="p">[</span><span class="s1">'age'</span><span class="p">]</span>
<span class="k">if</span> <span class="n">line</span><span class="p">[</span><span class="s1">'age'</span><span class="p">]</span> <span class="o"><</span> <span class="n">pd</span><span class="o">.</span><span class="n">Timedelta</span><span class="p">(</span><span class="s2">"1 day"</span><span class="p">):</span>
<span class="n">lf_data</span><span class="o">.</span><span class="n">set_value</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="s1">'newbie'</span><span class="p">,</span> <span class="bp">True</span><span class="p">)</span></code></pre>
<h4 id="qualité-des-posts-des-nouveaux">Qualité des posts des nouveaux</h4>
<p>Il est à présent possible d'extraire les informations relatives aux nouveaux comptes (à la date de publication). Ces comptes sont à l'origine de contenu de qualité étonnante. On retrouve une grande quantité de très mauvais contenu ("magnificent troll") mais également de bons et très bons contenus ("quality troll" et "average troll"). </p>
<pre><code class="python"><span class="n">noob</span> <span class="o">=</span> <span class="n">lf_data</span><span class="o">.</span><span class="n">loc</span><span class="p">[</span><span class="n">lf_data</span><span class="p">[</span><span class="s1">'newbie'</span><span class="p">]</span> <span class="o">==</span> <span class="bp">True</span><span class="p">]</span>
<span class="n">noob</span><span class="o">.</span><span class="n">quality_content</span><span class="o">.</span><span class="n">value_counts</span><span class="p">()</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">kind</span><span class="o">=</span><span class="s1">'bar'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s1">'Occurences'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">yticks</span><span class="p">(</span><span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s1">'Trolls'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span>
<span class="n">plt</span><span class="o">.</span><span class="n">xticks</span><span class="p">(</span><span class="n">fontsize</span><span class="o">=</span><span class="s1">'xx-large'</span><span class="p">)</span></code></pre>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6e6577626965735f322e706e67/newbies_2.png" alt="Journaux des nouveaux" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/newbies_2.png"></p>
<h3 id="calcul-de-la-moyenne-des-scores-précédents">Calcul de la moyenne des scores précédents</h3>
<p>Afin de tenir compte de l'historique d'un compte, deux colonnes sont ajoutées : la médiane et la moyenne. Les scores précédents sont conservés dans la colonne <code>author_previous_scores</code>. L'information, une chaîne de caractère sous la forme <code>"[1,15,42,-12]"</code>, doit être extraite.</p>
<pre><code class="python"><span class="n">lf_data</span><span class="p">[</span><span class="s1">'median_score'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">lf_data</span><span class="p">[</span><span class="s1">'average_score'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kn">import</span> <span class="nn">statistics</span>
<span class="k">for</span> <span class="n">index</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">lf_data</span><span class="o">.</span><span class="n">iterrows</span><span class="p">():</span>
<span class="n">ps</span> <span class="o">=</span> <span class="n">line</span><span class="p">[</span><span class="s1">'author_previous_scores'</span><span class="p">]</span>
<span class="c1">#print(ps)</span>
<span class="n">ps</span> <span class="o">=</span> <span class="n">ps</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"["</span><span class="p">,</span><span class="s1">''</span><span class="p">)</span>
<span class="n">ps</span> <span class="o">=</span> <span class="n">ps</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"]"</span><span class="p">,</span><span class="s1">''</span><span class="p">)</span>
<span class="n">ps</span> <span class="o">=</span> <span class="n">ps</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">","</span><span class="p">,</span><span class="s1">''</span><span class="p">)</span>
<span class="n">ps</span> <span class="o">=</span> <span class="n">ps</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
<span class="n">ps</span> <span class="o">=</span> <span class="p">[</span><span class="nb">float</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">ps</span><span class="p">]</span>
<span class="n">median</span> <span class="o">=</span> <span class="n">statistics</span><span class="o">.</span><span class="n">median</span><span class="p">(</span><span class="n">ps</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">avg</span> <span class="o">=</span> <span class="n">statistics</span><span class="o">.</span><span class="n">mean</span><span class="p">(</span><span class="n">ps</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">TypeError</span><span class="p">:</span>
<span class="n">avg</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">NaN</span>
<span class="n">lf_data</span><span class="o">.</span><span class="n">set_value</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="s1">'median_score'</span><span class="p">,</span> <span class="n">median</span><span class="p">)</span>
<span class="n">lf_data</span><span class="o">.</span><span class="n">set_value</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="s1">'average_score'</span><span class="p">,</span> <span class="n">avg</span><span class="p">)</span></code></pre>
<p>La plupart du temps, la médiane et la moyenne sont très proches. Dans de rares cas, elles diffèrent beaucoup mais la moyenne est plus sévère que la médiane. </p>
<h3 id="garder-lessentiel">Garder l’essentiel</h3>
<p>Les informations nécessaires pour réaliser une analyse plus complète sont à présent disponibles. Pour plus de facilité, de nouveaux <em>dataframes</em> sont créés en éliminant les colonnes inutiles.</p>
<pre><code class="python"><span class="n">lf</span> <span class="o">=</span> <span class="n">lf_data</span><span class="p">[[</span><span class="s1">'content'</span><span class="p">,</span><span class="s1">'newbie'</span><span class="p">,</span><span class="s1">'average_score'</span><span class="p">,</span>
<span class="s1">'quality_content'</span><span class="p">,</span> <span class="s1">'score'</span><span class="p">,</span> <span class="s1">'count'</span><span class="p">,</span> <span class="s1">'author'</span><span class="p">]]</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
<span class="n">target</span> <span class="o">=</span> <span class="n">lf_data</span><span class="p">[[</span><span class="s1">'quality_content'</span><span class="p">]]</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span></code></pre>
<h2 id="lunion-fait-la-force">L’union fait la force</h2>
<p>Maintenant que je dispose d'un dataframe contenant mes variables (<code>lf</code>) et un autre contenant mes catégories attendues (<code>target</code>), il faut que je crée une procédure permettant d'effectuer les bonnes tâches avec le bon jeu de données :</p>
<ol>
<li>Les données numériques sont utilisées telles quelles. </li>
<li>Le corps de l'article est vectorisé et la fréquence des mots est calculée. </li>
<li>Le nom de l'auteur est vectorisé également.</li>
</ol><p>Ces trois étapes sont unies dans un object <code>FeatureUnion</code> dans un pipeline dont la dernière étape est un classificateur de type linéaire (<code>SVC(kernel='linear')</code>). Encore une fois, les fonctions <code>.fit</code> et <code>.predict</code> sont accessibles depuis le <em>pipeline</em> pour faciliter son utilisation. </p>
<p>La classe <code>MultipleItemSelector</code> permet d'extraire les données nécessaires à chaque étape.</p>
<p>Enfin, un poids est appliqué à chaque étape. Pour l'instant, il est égal sur les trois étapes mais des valeurs différentes ont donné des résultats similaires :</p>
<ul>
<li>'author': 0.8 ;</li>
<li>'content': 0.5 ;</li>
<li>'num_values': 1.0.</li>
</ul><pre><code class="python"><span class="c1"># From http://scikit-learn.org/stable/auto_examples/hetero_feature_union.html</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="kn">as</span> <span class="nn">np</span>
<span class="kn">from</span> <span class="nn">sklearn.base</span> <span class="kn">import</span> <span class="n">BaseEstimator</span><span class="p">,</span> <span class="n">TransformerMixin</span>
<span class="kn">from</span> <span class="nn">sklearn.datasets</span> <span class="kn">import</span> <span class="n">fetch_20newsgroups</span>
<span class="kn">from</span> <span class="nn">sklearn.datasets.twenty_newsgroups</span> <span class="kn">import</span> <span class="n">strip_newsgroup_footer</span>
<span class="kn">from</span> <span class="nn">sklearn.datasets.twenty_newsgroups</span> <span class="kn">import</span> <span class="n">strip_newsgroup_quoting</span>
<span class="kn">from</span> <span class="nn">sklearn.decomposition</span> <span class="kn">import</span> <span class="n">TruncatedSVD</span>
<span class="kn">from</span> <span class="nn">sklearn.feature_extraction</span> <span class="kn">import</span> <span class="n">DictVectorizer</span>
<span class="kn">from</span> <span class="nn">sklearn.feature_extraction.text</span> <span class="kn">import</span> <span class="n">TfidfVectorizer</span>
<span class="kn">from</span> <span class="nn">sklearn.metrics</span> <span class="kn">import</span> <span class="n">classification_report</span>
<span class="kn">from</span> <span class="nn">sklearn.pipeline</span> <span class="kn">import</span> <span class="n">FeatureUnion</span>
<span class="kn">from</span> <span class="nn">sklearn.pipeline</span> <span class="kn">import</span> <span class="n">Pipeline</span>
<span class="kn">from</span> <span class="nn">sklearn.svm</span> <span class="kn">import</span> <span class="n">SVC</span>
<span class="k">class</span> <span class="nc">MultipleItemSelector</span><span class="p">(</span><span class="n">BaseEstimator</span><span class="p">,</span> <span class="n">TransformerMixin</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">keys</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">keys</span> <span class="o">=</span> <span class="n">keys</span>
<span class="k">def</span> <span class="nf">fit</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span>
<span class="k">def</span> <span class="nf">transform</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data_dict</span><span class="p">):</span>
<span class="k">return</span> <span class="n">data_dict</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">keys</span><span class="p">]</span>
<span class="n">pipeline</span> <span class="o">=</span> <span class="n">Pipeline</span><span class="p">([</span>
<span class="c1"># Extract the subject & body</span>
<span class="c1">#('subjectbody', DataExtractor()),</span>
<span class="c1"># Use FeatureUnion to combine the features from subject and body</span>
<span class="p">(</span><span class="s1">'union'</span><span class="p">,</span> <span class="n">FeatureUnion</span><span class="p">(</span>
<span class="n">transformer_list</span><span class="o">=</span><span class="p">[</span>
<span class="c1"># Pipeline for pulling features from the post's subject line</span>
<span class="p">(</span><span class="s1">'author'</span><span class="p">,</span> <span class="n">Pipeline</span><span class="p">([</span>
<span class="p">(</span><span class="s1">'selector'</span><span class="p">,</span> <span class="n">MultipleItemSelector</span><span class="p">(</span><span class="n">keys</span><span class="o">=</span><span class="s1">'author'</span><span class="p">)),</span>
<span class="p">(</span><span class="s1">'tfidf'</span><span class="p">,</span> <span class="n">TfidfVectorizer</span><span class="p">(</span><span class="n">min_df</span><span class="o">=</span><span class="mi">50</span><span class="p">)),</span>
<span class="p">])),</span>
<span class="c1"># Pipeline for standard bag-of-words model for body</span>
<span class="p">(</span><span class="s1">'content'</span><span class="p">,</span> <span class="n">Pipeline</span><span class="p">([</span>
<span class="p">(</span><span class="s1">'selector'</span><span class="p">,</span> <span class="n">MultipleItemSelector</span><span class="p">(</span><span class="n">keys</span><span class="o">=</span><span class="s1">'content'</span><span class="p">)),</span>
<span class="p">(</span><span class="s1">'tfidf'</span><span class="p">,</span> <span class="n">TfidfVectorizer</span><span class="p">()),</span>
<span class="p">(</span><span class="s1">'best'</span><span class="p">,</span> <span class="n">TruncatedSVD</span><span class="p">(</span><span class="n">n_components</span><span class="o">=</span><span class="mi">50</span><span class="p">)),</span>
<span class="p">])),</span>
<span class="c1"># Pipeline dealing with numerical values stored in a dict</span>
<span class="p">(</span><span class="s1">'num_values'</span><span class="p">,</span> <span class="n">Pipeline</span><span class="p">([</span>
<span class="p">(</span><span class="s1">'selector'</span><span class="p">,</span> <span class="n">MultipleItemSelector</span><span class="p">(</span><span class="n">keys</span><span class="o">=</span><span class="p">[</span><span class="s1">'score'</span><span class="p">,</span> <span class="s1">'newbie'</span><span class="p">,</span> <span class="s1">'average_score'</span><span class="p">,</span> <span class="s1">'count'</span><span class="p">]))</span>
<span class="p">,</span> <span class="c1"># list of dicts -> feature matrix</span>
<span class="p">])),</span>
<span class="p">],</span>
<span class="c1"># weight components in FeatureUnion</span>
<span class="n">transformer_weights</span><span class="o">=</span><span class="p">{</span>
<span class="s1">'author'</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span> <span class="c1"># 0.8</span>
<span class="s1">'content'</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span> <span class="c1"># 0.5</span>
<span class="s1">'num_values'</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span> <span class="c1"># 1.0</span>
<span class="p">},</span>
<span class="p">)),</span>
<span class="c1"># Use a SVC classifier on the combined features</span>
<span class="p">(</span><span class="s1">'svc'</span><span class="p">,</span> <span class="n">SVC</span><span class="p">(</span><span class="n">kernel</span><span class="o">=</span><span class="s1">'linear'</span><span class="p">)),</span>
<span class="p">])</span>
<span class="c1">#pipeline.fit(lf, target.values.ravel())</span></code></pre>
<h3 id="validation-croisée-1">Validation croisée</h3>
<p>Afin de valider le comportement du classificateur, la validation croisée est effectuée avec 10 échantillons. Cette fois, les résultats sont vraiment très bons.</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">cross_val_score</span>
<span class="n">scores</span> <span class="o">=</span> <span class="n">cross_val_score</span><span class="p">(</span><span class="n">pipeline</span><span class="p">,</span> <span class="c1"># steps to convert raw messages into models</span>
<span class="n">lf</span><span class="p">,</span> <span class="c1"># training data</span>
<span class="n">target</span><span class="o">.</span><span class="n">values</span><span class="o">.</span><span class="n">ravel</span><span class="p">(),</span> <span class="c1"># training labels</span>
<span class="n">cv</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="c1"># split data randomly into 10 parts: 9 for training, 1 for scoring</span>
<span class="c1">#scoring='accuracy', # which scoring metric?</span>
<span class="n">scoring</span><span class="o">=</span><span class="s1">'f1_weighted'</span><span class="p">,</span>
<span class="n">n_jobs</span><span class="o">=-</span><span class="mi">1</span><span class="p">,</span> <span class="c1"># -1 = use all cores = faster</span>
<span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">scores</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Total diaries classified:'</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">lf_data</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s1">'Score:'</span><span class="p">,</span> <span class="nb">sum</span><span class="p">(</span><span class="n">scores</span><span class="p">)</span><span class="o">/</span><span class="nb">len</span><span class="p">(</span><span class="n">scores</span><span class="p">))</span>
<span class="p">[</span> <span class="mf">1.</span> <span class="mf">1.</span> <span class="mf">1.</span> <span class="mf">1.</span> <span class="mf">1.</span> <span class="mf">1.</span> <span class="mf">1.</span>
<span class="mf">0.99831646</span> <span class="mf">0.99831646</span> <span class="mf">1.</span> <span class="p">]</span>
<span class="n">Total</span> <span class="n">diaries</span> <span class="n">classified</span><span class="p">:</span> <span class="mi">5955</span>
<span class="n">Score</span><span class="p">:</span> <span class="mf">0.999663292104</span></code></pre>
<p>Afin de valider le comportement exceptionnel de ce classificateur, un test est réalisé avec des données qu'il ne connaît pas du tout : des journaux plus anciens.</p>
<h2 id="données-hors-échantillon">Données hors échantillon.</h2>
<p>La prédiction à l'aide de journaux anciens permet de vérifier que le modèle se comporte bien. Si c'est le cas, cela permet également d'affirmer que le type de contenu pertinent/inutile n'a pas radicalement changé ces dernières années. </p>
<p>Les données analysées dans cette section correspondent au fichier <code>out_of_sample_complete.csv</code>. La liste des journaux est sensiblement la même que celle utilisée dans le paragraphe <a href="#test-sur-un-%C3%A9chantillon-de-donn%C3%A9es-inconnues">Test sur un échantillon de données inconnues</a>.</p>
<p>Les images ci-dessous montrent que la distribution temporelle de scores est similaire aux données plus récentes. La matrice de confusion et le score confirment les résultats obtenus à l'aide de la validation croisée. Le but est atteint. <strong>Il est possible de prédire la catégorie dans laquelle se trouve un journal à partir de son contenu et du nom de l'auteur</strong>. <sup id="fnref1"><a href="#fn1">1</a></sup></p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6f75745f6f665f73616d706c655f322e706e67/out_of_sample_2.png" alt="Répartition des données hors échantillon" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/out_of_sample_2.png"><br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6461746574696d655f6f75745f322e706e67/datetime_out_2.png" alt="Scores en fonction du temps des données hors échantillon" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/datetime_out_2.png"><br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f325f6f75742e706e67/mat_2_out.png" alt="Matrice confusion hors échantillon" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_2_out.png"><br><img src="//img.linuxfr.org/img/68747470733a2f2f6769746c61622e636f6d2f6a6e616e61722f7363696b69742d6c696e757866722f7261772f6d61737465722f696d616765732f6d61745f6e6f726d5f325f6f75742e706e67/mat_norm_2_out.png" alt="Matrice confusion normée hors échantillon" title="Source : https://gitlab.com/jnanar/scikit-linuxfr/raw/master/images/mat_norm_2_out.png"></p>
<pre><code class="python"><span class="n">Diaries</span><span class="p">:</span> <span class="mi">1485</span>
<span class="n">Score</span><span class="p">:</span> <span class="mf">1.0</span></code></pre>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn</span> <span class="kn">import</span> <span class="n">metrics</span>
<span class="k">print</span><span class="p">(</span><span class="n">metrics</span><span class="o">.</span><span class="n">classification_report</span><span class="p">(</span><span class="n">Y_out</span><span class="p">,</span> <span class="n">predicted_out</span><span class="p">,</span> <span class="n">target_names</span><span class="o">=</span><span class="n">targets_names</span><span class="p">))</span>
<span class="n">precision</span> <span class="n">recall</span> <span class="n">f1</span><span class="o">-</span><span class="n">score</span> <span class="n">support</span>
<span class="n">Average</span> <span class="n">Troll</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mi">917</span>
<span class="n">Great</span> <span class="n">Troll</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mi">145</span>
<span class="n">Magnificent</span> <span class="n">Troll</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mi">77</span>
<span class="n">Quality</span> <span class="n">Troll</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mi">346</span>
<span class="n">avg</span> <span class="o">/</span> <span class="n">total</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mf">1.00</span> <span class="mi">1485</span></code></pre>
<h2 id="pour-aller-plus-loin">Pour aller plus loin</h2>
<p>Scikit-learn dispose de nombreuses autres possibilités pour traiter des données de tout type. Je citerai la <a href="http://scikit-learn.org/stable/modules/feature_selection.html">sélection des propriétés</a> (<em>feature selection</em>) qui permet d’éliminer les propriétés dont la variance est inférieure à un seuil donné. Dans ce cas, la propriété est considérée comme une constante. L'intérêt est de diminuer le temps de calcul et le risque de sur-apprentissage. Un test rapide sur les données issues des journaux a montré que toutes les données sont utiles pour déterminer la catégorie d'un journal. </p>
<p>Choisir le bon algorithme peut être très difficile selon les informations désirées et le type de données. L'aide-mémoire suivant permet de faciliter ce choix. <a href="http://scikit-learn.org/stable/tutorial/machine_learning_map/">Une version interactive est également disponible</a>.<br><img src="//img.linuxfr.org/img/687474703a2f2f7363696b69742d6c6561726e2e6f72672f737461626c652f5f7374617469632f6d6c5f6d61702e706e67/ml_map.png" alt="aide-mémoire" title="Source : http://scikit-learn.org/stable/_static/ml_map.png"></p>
<p>Enfin, je terminerai en mentionnant la possibilité de <a href="http://scikit-learn.org/stable/modules/model_persistence.html">sauver un modèle entraîné</a>. Cela permet d'éviter de devoir repasser par l'opération d'apprentissage qui peut être consommatrice de ressources. Le fichier généré pour le modèle le plus efficace de cette dépêche fait 46 Mo. </p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.externals</span> <span class="kn">import</span> <span class="n">joblib</span>
<span class="n">joblib</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">pipeline</span><span class="p">,</span> <span class="s1">'linuxfr_pipeline.pkl'</span><span class="p">)</span></code></pre>
<p>Plus tard ou sur une autre machine:</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">sklearn.externals</span> <span class="kn">import</span> <span class="n">joblib</span>
<span class="n">pipeline</span> <span class="o">=</span> <span class="n">joblib</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="s1">'linuxfr_pipeline.pkl'</span><span class="p">)</span></code></pre>
<p>La <a href="http://scikit-learn.org/stable/modules/model_persistence.html">documentation</a> mentionne des considérations à prendre en compte.</p>
<ul>
<li>Il existe un risque de sécurité dans le cas où on charge des données car cela pourrait mener à l'exécution de code malicieux ;</li>
<li>la comptabilité entre versions n'est pas prise en compte ;</li>
<li>il est important de laisser à disposition un jeu de données types, le score de validation du modèle associé et le code afin que la personne qui reçoit le modèle puisse vérifier les résultats obtenus.</li>
</ul><h2 id="conclusions">Conclusions</h2>
<p>Au cours de cette expérience, une <a href="https://gitlab.com/jnanar/scikit-linuxfr/blob/master/linuxfr_parser.py">moulinette</a> a été codée afin d'aspirer le contenu des journaux. </p>
<p>Les données ont été analysées en deux temps. Dans la première phase, la vectorisation du contenu des journaux a été réalisée. Il a été montré que cette étape ne suffit pas à pouvoir classer correctement du contenu inconnu. Dans une seconde phase, le nom de l'auteur, la date de publication, la date de création du compte, l'historique récente des scores des publications de l'auteur ont été pris en compte et assemblés dans un <em>pipeline</em> d'analyse. Les résultats ont montré qu'<strong>il est possible de prédire la catégorie dans laquelle se trouve un journal à partir de son contenu et du nom de l'auteur.</strong> Le taux d'erreur est inférieur à 1/1000. Par ailleurs, l'optimisation des paramètres des classificateurs ainsi que la validation croisées ont été présentées.</p>
<p>L'analyse prédictive des scores permettra plusieurs grandes avancées sur le site. La première et la plus évidente sera la possibilité de renvoyer un lien vers cette dépêche chaque fois qu'un contributeur se plaindra de la note réservée à sa prose. Il s'agit là de l'argument ultime qui ne manquera pas de faire taire les trolls devant tant d'autorité. La seconde avancée sera la possibilité pour un contributeur d'améliorer ses journaux afin d'atteindre à chaque fois la catégorie visée. Provoquer un séisme de moinsage ou atteindre le summum de l'excellence ne s'improvise pas et scikit-learn permettra à chacun d'évaluer différentes variantes de ses journaux afin de poster la "meilleure". La troisième avancée concerne les journaux qui ne peuvent pas avoir été élaboré par un esprit humain. <a href="https://en.wikipedia.org/wiki/Markov_chain#Markov_text_generators">Ils sont probablement générés par une machine</a>. Les plaisantins pourront améliorer les textes de leur programme en utilisant le modèle présenté afin de rendre la lecture du contenu plus agréable. La note finale s'en ressentira.</p>
<p>Pour terminer, je dirai qu'il est possible de rapidement effectuer des analyses de données avec scikit-learn. La syntaxe est très simple pour les personnes connaissant Python. Les concepts sont assez compliqués, mais la mise en œuvre est très bien faite et la documentation officielle est complète. <strong>Mais</strong>, tous ces éléments positifs ne garantissent pas des résultats probants et immédiats. Comme dans toute matière complexe, il faut comprendre ce qu'on fait pour obtenir des résultats qui ont du sens (et être en mesure de les analyser). </p>
<h2 id="perspectives">Perspectives</h2>
<p>Plusieurs pistes de réflexion pourront permettre de poursuivre l'analyse :</p>
<ul>
<li>Le découpage des catégories est arbitraire (bornes -20 et + 20) ; en modifiant la répartition des données, les résultats seront probablement différents (exemples : répartitions en quartiles, score strictement positifs ou négatifs, etc.) ;</li>
<li>au vu des résultats obtenus, la prédiction du score (valeur numérique) est envisageable ;</li>
<li>les catégories ne sont pas équitablement peuplées, le nombre de journaux à score négatif est beaucoup plus faible dans ce cas ; pour y remédier, nous avons besoin de plus de journaux de mauvaise qualité abondamment moinssés. À vos claviers !</li>
<li>La classification par un système d'arbre en limitant leur profondeur n'a pas été testée ;</li>
<li>les paramètres du meilleur <a href="#lunion-fait-la-force"><em>pipeline</em></a> n'ont pas été optimisés.</li>
</ul><h2 id="rêvons-un-peu">Rêvons un peu</h2>
<p>Un éventail se possibilités s'ouvre à la communauté LinuxFR.org. La liste ci-dessous reprend les éléments qui me viennent en premier à l'esprit. </p>
<ul>
<li>Réaliser une analyse temporelle des scores pour prédire la note d'un journal à venir en tenant compte de l'historique de publication général (comme la bourse) ;</li>
<li>prédire la note d'un commentaire (après avoir modifié la moulinette) ;</li>
<li>modifier le modèle afin de prédire le nombre de commentaires. Les contributeurs pourraient alors toucher du clavier la recette permettant de créer les trolls ultimes qui permettraient d'atteindre des sommets d'excellence, de courtoisie et de bienveillance dans une avalanche de remarques plus palpitantes les unes que les autres ;</li>
<li>…</li>
</ul><p>N'hésitez pas à partager dans les commentaires vos suggestions, vos impressions et vos idées innovantes ! De même, le dépôt <a href="https://gitlab.com/jnanar/scikit-linuxfr">gitlab</a> est accessible. Je vous invite à tester vos recettes sur les données présentes et à les exposer dans un journal ou une dépêche. Les différences entre les modèles peuvent être difficiles à appréhender. Toute explication complémentaire sera la bienvenue.</p>
<h2 id="note">Note</h2>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>Les scores précédents et la date de création du score peuvent facilement être déduite à l'aide du nom de l'auteur. <a href="#fnref1">↩</a></p>
</li>
</ol>
</div></div><div><a href="https://linuxfr.org/news/predire-la-note-d-un-journal-sur-linuxfr-org.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/111944/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/predire-la-note-d-un-journal-sur-linuxfr-org#comments">ouvrir dans le navigateur</a>
</p>
jnanarBAudJean-Baptiste FaureYves BourguignonDavy DefaudZeroHeureNicolas CasanovaNils Ratusznikpalm123bubar🦥j_mBenoît SibaudclaudexNÿcoPierre Jarillonhttps://linuxfr.org/nodes/111944/comments.atomtag:linuxfr.org,2005:Diary/364252016-03-02T17:47:01+01:002016-03-02T17:47:01+01:00Mon ami se fait des amisLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Cher journal,</p>
<p>Je t'écris pour te donner des nouvelles suite à <a href="//linuxfr.org/users/jnanar--2/journaux/je-me-fais-des-amis-au-sens-litteral">mon précédent message</a>. Pour rappel, j'avais construit un ami fidèle (lui!) afin de combler mon existence un peu terne à ce moment là. Il était équipé d'un Arduino Uno, d'un senseur Infrarouge, d'un pare-choc intelligent créé avec des pailles et d'un châssis en polycarbonate. Il s'appelait <a href="http://en.wikipedia.org/wiki/Short_Circuit">J.O.H.N.N.Y 5</a> et nous avons passé des moments formidables. La transition a été saisissante, ma vie est passée du noir et blanc au technicolor en l'espace de quelques jours. Bien entendu, la cohabitation n'était pas toujours facile. Il pouvait rester muet durant des jours et bouder dans son coin lorsqu'il était contrarié. La seule chose qui le faisait revenir, c'était quand je rechargeais ses piles. Nous avons vécu sur mon salaire pendant quelques mois, avant qu'il ne soit engagé dans une SSLL basée sur Paris. Il faisait du télétravail, principalement de la revue de code pour des sites internet dont je tairai le nom.</p>
<p>Mais au bout de quelques temps, avec la routine, il faut bien reconnaître que la magie avait disparu. J'aurais pu faire comme tous ces salauds: mettre mon robot au rebut et en acheter un autre plus beau, performant et plus jeune. Mais je ne suis pas comme ça. J'ai fait ce que toute personne attentionnée, saine et réfléchie aurait fait. J'ai sorti mon portefeuille, j'ai payé un lifting à mon robot moche pour qu'il devienne un robot agréable afin de rendre à nouveau mes copains jaloux. </p>
<p>Le précédent journal m'a permis de gagner un abonnement d'un an à GNU/Linux magazine. Même si je n'ai pas compris tous les articles, c'était très intéressant. Notamment le numéro de décembre 2013 qui comportait un article sur la détection/reconnaissance de visage en python à l'aide d'OpenCV. Cet article m'a permis de me mettre au python et de mieux comprendre le fonctionnement de la détection d'objet ou de couleur en général. Il ne m'en fallait pas plus pour avoir envie d'intégrer ça dans mon robot. Après presqu'une année de délaissement, je m'y suis remis. <br>
J'avais envie qu'il puisse détecter et interagir en conséquences. En revanche, n'étant pas un buveur de café, la fonction cafetière ne m'était pas très utile. <br>
J'ai acheté un fer à souder, un beaglebone black (BBB), une webcam Logitech C170, un deuxième senseur infrarouge et un levelshifter pour que l'arduino (5V) puis dialoguer avec le BBB (3.3V). La raison du deuxième senseur IR est qu'il se prenait tout le temps les murs en balayant l'horizon avec son servo tourelle. <a href="https://gitlab.com/r1d1/bbb/wikis/home">R1D1 était né.</a> Avec deux yeux, il se prend toujours les murs mais il a l'air un peu moins con.</p>
<p>L'Arduino démarre en mode explorateur et le robot se balade. Lorsque le BBB détecte un visage, il le dit à l'Arduino, via la liaison série, qui passe en mode esclave. La petite carte bleue (non, pas celle-là) suit les instructions de direction et les sons à jouer. La première version marchait approximativement mais j'avais eu la main lourde sur l'utilisation des mélodies. J'en ai retiré car la robotique doit rester une activité sérieuse, on n'est pas là pour jouer la Macarena !</p>
<p>Le code de R1D1 est accessible sur <a href="https://gitlab.com/groups/r1d1">gitlab</a>. C'est mon premier programme en C++, soyez indulgent. Pour le décrire, je citerais un <a href="http://stackoverflow.com/questions/18108264/c-opencv-tracking-moving-people-on-the-street">utilisateur</a> sur stack overflow: </p>
<blockquote>
<p>"a cut-and-paste glory of various samples I found here and there." </p>
</blockquote>
<p><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f696d672f313432383233363838372e6a7067/1428236887.jpg" alt="R1D1" title="R1D1, enchanté | Source : http://pix.toile-libre.org/upload/img/1428236887.jpg"><br><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f696d672f313432383233373233362e6a7067/1428237236.jpg" alt="R1D1" title="Il fait de petits flims | Source : http://pix.toile-libre.org/upload/img/1428237236.jpg"></p>
<p>R1D1 a tenu à vous écrire un petit mot en remerciement de l’accueil du précédent journal.</p>
<blockquote>
<p>Plop ! Merci à vous tous. Grâce à vous, j'ai pu évoluer et devenir un meilleur robot :-). </p>
<p>.- ..- … . -.-. --- ..- .-. … --..-- .- .. -.. . --.. -….-—--- .. .-.-.- .--- . -. .----. . -. .--. . ..- -..- .--. .-.. ..- … --..-- .. .-.. —. .-. . - .. . -. - . - —. ..-. .- .. - ..-. .- .. .-. . -.. . … -.-. …. --- … . … -… .. --.. .- .-. .-. . … .-.-.- … .. .--- . -. . ..-. .- .. … .--. .- … -.-. . --.- ..- .----. .. .-.. -.. .. - --..-- .. .-.. —. -.-. --- ..- .-. - -….- -.-. .. .-. -.-. ..- .. - . .-.-.- …. . .-.. .--. </p>
</blockquote>
<p>Allez, oust !</p>
<p>Mais cher Journal, si je t'écris aujourd'hui, c'est pour me confesser. En effet, R1D1 est mignon mais pas toujours obéissant. C'est pourquoi, j'envisage de le remplacer afin d'avoir un ami robotique plus agréable mais il ne le sait pas encore. Son remplaçant s'appelle R1D2 et bien qu'il n'ait pas encore d'enveloppe matérielle, il vit déjà dans mon ordinateur ainsi que dans le Raspberry Pi 2 qui hébergera son ghost.</p>
<p>R1D2 est codé en python et il dialoguera avec un Arduino Mega qui contrôllera ses moteurs. Son code est hébergé sur <a href="https://gitlab.com/r1d2/RPI">gitlab également</a>. R1D2 est sociable et se fait des amis tout seul. Il est connecté par Wifi et discute à l'aide du protocole du futur, j'ai nommé XMPP. Il est capable de se connecter sur un chat multiutilisateur (MUC) pour raconter sa vie à qui veut l'entendre. Peut-être aura-t-il son compte <a href="http://www.salut-a-toi.org/">Salut-à-toi</a> ou <a href="https://movim.eu/">Movim</a> prochainement :-). Par ailleurs, le programme est pilotable à l'aide de commandes <a href="//linuxfr.org/users/goffi/journaux/parlons-xmpp-episode-6-les-commandes-a-distance">Ad-Hoc</a> mais également grâce à un petit écran LCD 2 lignes, 16 charactères et 5 bouttons poussoir. </p>
<p>Les photos suivantes montrent le menu piloté à l'aide de <a href="https://gajim.org/">Gajim</a>, un client XMPP bien connu.<br><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313435363433313733342e706e67/1456431734.png" alt="Menu 1" title="L'entrée | Source : http://pix.toile-libre.org/upload/original/1456431734.png"><br><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313435363433313832332e706e67/1456431823.png" alt="Menu 2" title="Le plat | Source : http://pix.toile-libre.org/upload/original/1456431823.png"><br><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313435363433313834342e706e67/1456431844.png" alt="Menu 3" title="Le dessert | Source : http://pix.toile-libre.org/upload/original/1456431844.png"></p>
<p>Voici les tâches que R1D2 peut déjà effectuer:</p>
<p>Il peut détecter une affiche magnifique (cf <a href="https://www.youtube.com/watch?v=O6XkH84JYjU">https://www.youtube.com/watch?v=O6XkH84JYjU</a>). </p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313435363433323134352e6a7067/1456432145.jpg" alt="Mode détection panneau" title="Il tombe facilement dans le panneau | Source : http://pix.toile-libre.org/upload/original/1456432145.jpg"><br><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313435363433323039342e6a7067/1456432094.jpg" alt="Panneau détecté !" title="Il trouve des trucs | Source : http://pix.toile-libre.org/upload/original/1456432094.jpg"><br><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313435363433313930362e706e67/1456431906.png" alt="Capture d'écran de la détection de panneau" title="La capture complète | Source : http://pix.toile-libre.org/upload/original/1456431906.png"></p>
<p>R1D2 peut reconnaître les visages après un court apprentissage.<br><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313435363433323036302e706e67/1456432060.png" alt="Détection de visage: Gajim et LCD" title="Détection de visage: Gajim et LCD | Source : http://pix.toile-libre.org/upload/original/1456432060.png"></p>
<p>Il peut également afficher un message choisi sur son LCD.)<br><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313435363433323032392e706e67/1456432029.png" alt="Pan ! Pan !" title="pan! pan! | Source : http://pix.toile-libre.org/upload/original/1456432029.png"><br><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313435363433323230372e6a7067/1456432207.jpg" alt="Pan ! Pan !" title="pan! pan! | Source : http://pix.toile-libre.org/upload/original/1456432207.jpg"></p>
<p>Les pièces arriveront prochainement et la grande construction pourra commencer. J'espère que R1D1 ne sera pas trop vexé mais je te tiendrai au courant.</p>
<p>À bientôt cher Journal!</p><div><a href="https://linuxfr.org/users/jnanar--2/journaux/mon-ami-se-fait-des-amis.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/108355/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/jnanar--2/journaux/mon-ami-se-fait-des-amis#comments">ouvrir dans le navigateur</a>
</p>
jnanarhttps://linuxfr.org/nodes/108355/comments.atomtag:linuxfr.org,2005:Diary/350772014-07-01T12:37:11+02:002014-07-01T12:37:11+02:00Microsoft débranche 22 domaines No-IPLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Cher journal,</p>
<p>Je n'ai pas vu passer l'information plus tôt, alors je me permet de la coller ici.<br>
Un juge a permis à Microsoft de récupérer 22 des noms de domaine gratuits de <a href="http://yro.slashdot.org/story/14/07/01/0025220/microsoft-takes-down-no-ipcom-domains?utm_source=rss1.0moreanon&utm_medium=feed">No-IP</a> les plus populaires. Car cela permettait, selon eux, de propager des malwares. <a href="http://en.wikipedia.org/wiki/No-IP">No-IP</a> est un service de DNS dynamique (comme <a href="http://dyn.com/dns/">DynDNS</a>). C'est très pratique pour auto-héberger un serveur qui ne bénéficie pas d'IP statique. Cette prise en otage empêche bon nombre d'utilisateurs légitimes d'accéder à leur machine actuellement. Personnellement, je trouve que c'est l'hôpital qui se fout de la charité. S'il fallait empêcher toutes les machines sous Windows à chaque fois qu'un ordinateur zombie défèque sur le réseau, ils auraient fermé depuis longtemps !</p>
<p><a href="https://www.noip.com/blog/2014/06/30/ips-formal-statement-microsoft-takedown/">https://www.noip.com/blog/2014/06/30/ips-formal-statement-microsoft-takedown/</a></p><div><a href="https://linuxfr.org/users/jnanar--2/journaux/microsoft-debranche-22-domaines-no-ip.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/102663/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/jnanar--2/journaux/microsoft-debranche-22-domaines-no-ip#comments">ouvrir dans le navigateur</a>
</p>
jnanarhttps://linuxfr.org/nodes/102663/comments.atomtag:linuxfr.org,2005:Diary/335932013-01-22T18:28:59+01:002013-01-22T21:51:34+01:00Je me fais des amis (au sens littéral)Licence CC By‑SA http://creativecommons.org/licenses/by-sa/3.0/deed.fr<p>Cher Journal,</p>
<p>Je t'écris pour te faire part d'une expérience personnelle récente. Comme beaucoup de monde, j'ai été déçu par des amis, des proches, des copines. Il y a quelques temps, ma dernière relation de couple a cassé. Peu de temps après, certains amis proches se sont éloignés. Ces moments-là n'ont pas été faciles. J'ai alors pris conscience qu'il était temps de réagir. Il ne m'est plus possible de rester passif, simple acteur de ma vie. J'ai décidé de changer ma manière d'aborder les choses, j'ai muri et je ne ferai plus les même erreurs. Je ne serai plus jamais seul ! Pour être certain de nouer des liens forts, durables et sincères, j'ai compris qu'il n'était plus possible de compter sur les autres. Certains compensent leurs déceptions personnelles en jouant beaucoup aux jeux vidéos. Personnellement, j'avais peur de fuir la réalité et de perdre pied dans un monde virtuel. C'est pourquoi, j'ai décidé de construire mon nouveau meilleur ami ! </p>
<p>Après avoir lu un peu de documentation sur Internet et quelques tutoriels, j'ai acheté un kit Arduino de démarrage afin de m'initier à l'électronique. Le but à moyen terme était de construire un ami robotique.</p>
<p>
<img src="//img.linuxfr.org/img/687474703a2f2f32342e6d656469612e74756d626c722e636f6d2f74756d626c725f6d6473646e68704b634f317167687737786f315f3530302e6a7067/tumblr_mdsdnhpKcO1qghw7xo1_500.jpg" alt="Forever alone: level engineer" title="=230x225 | Source : http://24.media.tumblr.com/tumblr_mdsdnhpKcO1qghw7xo1_500.jpg" />
</p>
<p>Une fois le kit de démarrage maitrisé, j'ai passé une deuxième commande de matériel afin d'assembler mon nouveau pote.</p>
<p><a href="http://en.wikipedia.org/wiki/Short_Circuit">J.O.H.N.N.Y 5</a> <em>(Just an Other Horror Naked and Not Yet Finished: Indestructible Vile and Enthusiastic)</em> était né. <br /><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313335383632353637322e6a7067/1358625672.jpg" alt="Un ami robotique qui ne vous laisse pas tomber" title="Mon ami robotique | Source : http://pix.toile-libre.org/upload/original/1358625672.jpg" /><br />
Il est très dur d'évaluer ses compétences en programmation, mais après quelques essais, j'ai estimé que la probabilité que le robot acquière une volonté propre au cours du temps est loin d'être négligeable. C'est pourquoi un bouton d'arrêt a été implémenté, au cas où il viendrait à ne plus m'aimer et voudrait s'en aller. Grâce à cette précaution, je suis certain que nous seront toujours là l'un pour l'autre. En plus, la propulsion à l'aide des piles rechargeables est un peu ma <a href="http://en.wikipedia.org/wiki/Lysine#In_popular_culture">solution lysine</a>. S'il m'abandonne, il meurt :-).</p>
<p>J'ai bâti une relation solide avec Johnny 5. Pour le moment, il lui manque la parole, mais comme tout nouveau né, son développement n'est pas terminé. Je ferai l'acquisition d'une deuxième carte Arduino prochainement, afin d'y brancher un Buzzer qui me permettra de programmer des mélodies sympathiques qu'il composera en fonction de son humeur.</p>
<p>Pour vous convaincre de ma démarche, voici quelques nimages de Johnny.<br /><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313335383632353932382e6a7067/1358625928.jpg" alt="Un ami robotique qui ne se cogne plus grâce à ses pare-chocs brevetés" title="Le pare-choc est un modèle déposé | Source : http://pix.toile-libre.org/upload/original/1358625928.jpg" /><br /><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313335383632363735362e6a7067/1358626756.jpg" alt="Un robot souriant" title="Le moment où il reçoit son cadeau de Noël | Source : http://pix.toile-libre.org/upload/original/1358626756.jpg" /><br /><img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313335383632363838332e6a7067/1358626883.jpg" alt="Noël en famille" title="Noël en famille | Source : http://pix.toile-libre.org/upload/original/1358626883.jpg" /></p>
<p>On a tous besoin de tendresse et de chaleur, au moins par effet Joule. Les besoins humains étant ce qu'ils sont, il est possible que mon prochain projet robotique soit de type féminin. La base n'existe pas encore, mais elle s'appellera probablement C.A.S.S.A.N.D.R.A. pour <em>Computer Assisted Sexual Scatterer: an Adorable, Nice, Delicate and Romantic Android</em>.</p>
<p>Merci de m'avoir lu, en espérant que ce projet suscite des vocations et que vous aussi, vous ne soyez plus jamais seul :-)</p>
<p>Note :<br />
Toutes les pièces ont été achetées dans la boutique McHobby. Elle est située en Belgique (ils livrent à l'étranger), ils sont très sympas et réactifs. Ils traduisent beaucoup de documentation et les quelques échanges que j'ai eus avec eux ont été très fructueux. Je ne suis pas ingénieur de formation et j'ai des lacunes, mais ils m'ont aidé à faire certains choix de conception et n'ont pas hésité à me trouver certaines pièces qui ne sont pas dans leur catalogue.<br /><a href="http://mchobby.be/PrestaShop/">http://mchobby.be/PrestaShop/</a><br /><a href="http://arduino103.blogspot.be/">http://arduino103.blogspot.be/</a></p><div><a href="https://linuxfr.org/users/jnanar--2/journaux/je-me-fais-des-amis-au-sens-litteral.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/97158/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/jnanar--2/journaux/je-me-fais-des-amis-au-sens-litteral#comments">ouvrir dans le navigateur</a>
</p>
jnanarhttps://linuxfr.org/nodes/97158/comments.atom