tag:linuxfr.org,2005:/tags/machinelearning/publicLinuxFr.org : les contenus étiquetés avec « machinelearning »2020-06-27T14:29:46+02:00/favicon.pngtag:linuxfr.org,2005:News/399222020-06-22T11:38:54+02:002020-06-23T08:50:38+02:00Conférence SophiaConf le 29 et 30 juin 100 % en ligneLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><h2 id="toc-retours-dexpérience-et-partage-dexpertise">Retours d’expérience et partage d’expertise</h2>
<h3 id="toc-quatre-webconférences-sur-deux-jours-ia-conteneurs-covid19-etsécurité">Quatre webconférences sur deux jours : IA, conteneurs, Covid‑19 et sécurité</h3>
<p>Telecom Valley annonce la programmation de SophiaConf 2020, les 29 et 30 juin prochains de 17 h à 19 h, sous un format 100 % numérique, pour s’adapter au contexte sanitaire et économique incertain, tout en restant le rendez‑vous annuel estival des acteurs de l’Open Source azuréens. Des experts locaux partageront des retours d’expérience sur l’Open Source autour de quatre thèmes : Intelligence artificielle, Conteneurs, Covid‑19 et Sécurité.</p>
<p>La côte d’Azur possède de nombreux acteurs et consommateurs du logiciel libre, et cette année encore, SophiaConf va réunir ces pépites locales pour partager leurs expériences autour du Covid‑19, de l’intelligence artificielle, des conteneurs et de la cybersécurité, réparties en deux soirées de conférences.</p>
</div><ul><li>lien nᵒ 1 : <a title="http://www.sophiaconf.fr/" hreflang="fr" href="https://linuxfr.org/redirect/106494">Détails et inscription SophiaConf</a></li><li>lien nᵒ 2 : <a title="http://www.telecom-valley.fr/" hreflang="fr" href="https://linuxfr.org/redirect/106495">À propos de l’association Telecom Valley</a></li></ul><div><p><em>Bonjour aux amoureux du Libre, je vous partage l’info sur SophiaConf qui aura lieu très bientôt, et qui a l’avantage cette année d’être librement accessible par n’importe qui dans le monde entier grâce à la magie des webinar. SophiaConf c’est le plus gros évènement annuel du Libre sur la côte d’Azur, normalement en présentiel, mais on a tenu cette année à le maintenir malgré le changement forcé de schéma de diffusion (un peu comme OW2, par exemple !). En vous espérant nombreux !</em></p>
<h3 id="toc-lundi-29juin-à17h-opensource-covid19-etsécurité">Lundi 29 juin à 17 h : Open Source, Covid‑19 et Sécurité</h3>
<p>La première soirée sera centrée sur la sécurité au sens large. La sécurité informatique d’abord, avec un retour d’expérience sur <strong>Keycloak en multi‑site</strong> proposée par Outpost24. La sécurité sanitaire ensuite, avec un cas d’usage du <strong><em>machine learning</em> pour la prédiction du Covid‑19</strong> mis en œuvre par MyDataModels, et les <strong>actions en Internet des objets des <em>makers</em> de SoFAB</strong> pour la protection des personnes, grâce à UCA/I3S, Nuxperia et SoFAB.</p>
<h3 id="toc-mardi-30juin-à17h-opensource-intelligence-artificielle-etconteneurs">Mardi 30 juin à 17 h : Open Source, Intelligence Artificielle et Conteneurs</h3>
<p>Cette seconde soirée portera sur le monde des conteneurs. D’une part, avec deux interventions sur <strong>Kubernetes</strong> : l’une, proposée par Orange, traitant de <strong>Kubeflow</strong> et donc de <em>machine learning</em>, et l’autre, par David de Datadog, traitant d’<strong><em>autoscaling</em></strong>.<br>
D’autre part, avec une intervention de Red Hat qui nous fera découvrir les <strong><em>Kata Containers</em></strong>.</p>
</div><div><a href="https://linuxfr.org/news/conference-sophiaconf-le-29-et-30-juin-100-en-ligne.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/120862/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/conference-sophiaconf-le-29-et-30-juin-100-en-ligne#comments">ouvrir dans le navigateur</a>
</p>
Datafarian00Pierre JarillonDavy Defaudhttps://linuxfr.org/nodes/120862/comments.atomtag:linuxfr.org,2005:Diary/390872020-04-21T14:57:32+02:002020-04-21T14:57:32+02:00Revue (pas du tout exhaustive) de livres orientés machine learning / deep learningLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Chère linuxfrienne, cher linuxfrien,<br>
voici une petite dizaine d'année que l'"IA" a commencé à diffuser hors de la sphère des scientifiques/développeurs/experts et a commencé à faire la une d'articles plus ou moins grand public.<br>
Étant d'un naturel curieux et, profitant de cette période d'accalmie forcée, je me suis lancé dans lecture d'ouvrages dédiés à l'apprentissage automatique (machine learning) et l'apprentissage profond (deep learning, DL) en Python. Bon en fait j'ai commencé avant, mais on s'en fout.</p>
<p>À toutes fins utiles j'ai pensé partager ici mon ressenti sur ces différents ouvrages. Alors attention, je ne suis pas développeur professionnel et encore moins expert en "IA". De plus il existe une quantité pléthorique de tutoriels, sites web, vidéos, livres dédiés au ML et au DL. Tout ce que suit n'est que mon avis sur un nombre restreint de ressources, mais, cher lecteur, si tu as un profil analogue au mien, à savoir recherche académique + calcul scientifique + un goût prononcé pour le logiciel libre, alors ce qui suit pourrait t’intéresser. Je vais commencer par ceux traitant du machine learning, puis j'aborderai ceux traitant de deep learning.</p>
<p>PS: dans la mesure du possible j'essaye de fournir les liens vers les éditeurs des livres en question ou les sites web orignaux. Bien entendu, la plupart de ces ouvrages sont disponibles chez votre revendeur préféré.</p>
<ul>
<li><p><a href="https://jakevdp.github.io/PythonDataScienceHandbook/">"Data science handbook" par Jake VanderPlas</a> (en anglais): <br>
Livre disponible à l'achat, mais l'auteur le met gracieusement à disposition sur son site, de même que <a href="https://github.com/jakevdp/PythonDataScienceHandbook">son dépôt git</a> contenant les notebooks Jupyter. Ce livre n'est pas à proprement parler focalisé sur le machine learning, mais ce sujet fait l'objet du dernier chapitre. Le reste de cet ouvrage est excellent pour acquérir les bases du calcul scientifique en Python. Pour ceux déjà familiers du domaine ça fait un bon ouvrage de référence, en complément du très exhaustif <a href="https://scipy-lectures.org/">SciPy lecture notes</a>. J’ajoute que l’auteur du livre tient également un <a href="https://jakevdp.github.io/">blog</a> Python de très bon niveau (même si celui-ci semble au point mort depuis 2018).</p></li>
<li><p><a href="https://www.dunod.com/sciences-techniques/machine-learning-avec-scikit-learn-mise-en-oeuvre-et-cas-concrets-0">Machine Learning avec Scikit-Learn par Aurélien Géron</a> (en français), ainsi que <a href="https://www.dunod.com/entretien-avec-aurelien-geron-deep-learning-tensorflow">Deep Learning avec TensorFlow, du même auteur</a> (en français) ; les deux ouvrages sont disponibles groupés dans une édition mise à jour (en anglais): <a href="http://shop.oreilly.com/product/0636920142874.do">Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow</a>. Je n'ai lu que le premier de ces livres, je ne m'exprimerai donc que sur celui-ci, à savoir celui traitant de la bibliothèque Scikit-Learn. Ici l'auteur propose une revue (non-exhaustive mais déjà <em>très</em> riche) des algorithmes de machine learning appliqués à plusieurs cas très concrets (notamment, mais pas exclusivement, les grands classiques tels que la reconnaissance des chiffres de la base MNIST et la classification des iris sur la base de la longueur et la largeur de pétales). Les notebooks sont très bien faits et disponibles sur <a href="https://github.com/ageron/handson-ml2">le github de l'auteur</a>. Ne pas se laisser déconcerter par les 2 premiers chapitres où l'auteur utilise les algorithmes sans en expliquer le fonctionnement, ce qui risque de donner une impression de boîte noire. L'auteur revient sur le fonctionnement des algorithmes dans les chapitres ultérieurs. Pour ma part je suis resté un peu sur ma faim sur les aspects mathématiques qui sont peu détaillés, mais je suis conscient qu'il est difficile de trouver un équilibre entre la théorie et l'application concrète des algorithmes dans un nombre de page donné, surtout lorsqu'il s'agit d'une bibliothèque aussi massive que Scikit-Learn. En dehors de cette réserve personnelle, ce livre permet rapidement de comprendre les bases du machine learning et de l'appliquer à des cas simples. Je recommande.</p></li>
<li><p><a href="https://www.oreilly.com/library/view/data-science-from/9781491901410/">Data Science from Scratch par Joel Grus</a> (en anglais). Ce livre est un tour de force dans son genre car, comme son nom l'indique l'auteur ré-implémente tous les algorithmes à partir de zéro en Python pur (!). Étant un habitué de NumPy j'ai abandonné la lecture au bout de 8 chapitres sur les 27, le python pur ne facilitant pas vraiment la lisibilité pour ce qui me concerne. De plus, la réutilisation des algorithmes en question reste limitée car chacun connaît les limites de Python pur en termes de performances. J'ai aussi des réserves sur le côté pédagogique de l'exercice en ce sens qu'il pousse à réinventer la roue plutôt que d'utiliser des bibliothèques bien établies. Je ne recommande pas.</p></li>
</ul>
<p>Passons maintentant au deep-learning.<br>
- <a href="https://www.manning.com/books/deep-learning-with-python">Deep Learning with Python par François Chollet</a>. L'auteur est le créateur de Keras qui est sans doute la bibliothèque la plus user-friendly pour débuter dans le deep-learning. Keras est en quelque sorte un front-end à d'autres bibliothèques de deep learning telles que TensorFlow ou Theanos. L'auteur a fait le choix délibéré de ne pas détailler les aspects mathématiques et présente le fonctionnement des algorithmes par l'exemple. Pour les maths il faudra aller voir ailleurs. J'y reviens ci-dessous. Là encore, <a href="https://github.com/fchollet/deep-learning-with-python-notebooks">les notebooks sont disponibles</a>. Alors je n'irai pas par quatres chemins ; ce bouquin est tout simplement excellent: très pédagogique, les codes sont expliqués ligne par ligne, et la puissance de Keras est évidente. Chaque conclusion de chapitre et sous-chapitre résume les concepts clés à retenir. Des tableaux pratiques recensant quels algos utiliser dans quels cas sont éalement donnés. J'en suis au 2/3 et ça commence à se corser un peu (ça parle de traitement du langage, ce qui est un peu trop éloigné de mon domaine), mais j'ai bien l'intention d'aller jusqu'au bout. Je recommande.<br>
- complément indispensable au livre précédent pour ceux qui souhaitent comprendre les fondements mathématiques, le livre en ligne <a href="http://neuralnetworksanddeeplearning.com/">Neural Networks and Deep Learning</a>. Tout y est expliqué de façon très pédagogique: les fonctions d'activation, les fonctions de coûts, la rétro-propagation, les techniques de régularisation, jusqu'aux réseaux convolutifs. Les notebooks originaux sont dispos en <a href="https://github.com/mnielsen/neural-networks-and-deep-learning">version Python 2.7</a> et <a href="https://github.com/MichalDanielDobrzanski/DeepLearningPython35">mis à jour en Python3</a>. A lire absolument !<br>
- j'ai découvert le site précédent en regardant les vidéos de l'<em>excellente</em> chaine <a href="https://www.3blue1brown.com/">3blue1brown</a>, en particulier la <a href="https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi">série dédiée aux réseaux de neurones</a>. </p>
<p>En bonus, la bible du deep learning: <a href="https://www.deeplearningbook.org/">https://www.deeplearningbook.org/</a><br>
Pas (encore) lu mais c'est, paraît-il, un incontournable.</p>
<p>Bonne lecture :-)</p>
<div><a href="https://linuxfr.org/users/aboulle/journaux/revue-pas-du-tout-exhaustive-de-livres-orientes-machine-learning-deep-learning.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/120119/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/aboulle/journaux/revue-pas-du-tout-exhaustive-de-livres-orientes-machine-learning-deep-learning#comments">ouvrir dans le navigateur</a>
</p>
aboullehttps://linuxfr.org/nodes/120119/comments.atomtag:linuxfr.org,2005:Diary/389552020-02-20T17:15:33+01:002020-02-20T17:15:33+01:00Vélib' et open dataLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Ce court journal pour vous présenter un petit dataset qui pourrait intéresser certain.e.s d'entre vous (les plus parisien.ne.s):</p>
<p>J'ai récolté l'historique des disponibilités de vélos sur l'ensemble du réseau <a href="https://www.velib-metropole.fr/">Vélib'</a> depuis décembre 2019.</p>
<p>Le résultat est sur le dépôt github suivant: <a href="https://github.com/lovasoa/historique-velib-opendata"><strong>lovasoa/historique-velib-opendata</strong></a>.</p>
<h3 id="toc-pourquoi">Pourquoi ?</h3>
<p>Paris <a href="https://opendata.paris.fr/explore/dataset/velib-disponibilite-en-temps-reel/information/">met à disposition</a> sous licence <a href="https://opendatacommons.org/licenses/odbl/">ODBL</a> la disponibilité des vélibs en temps réel. Mais elle ne fournit aucun moyen d'accéder à l'historique de ces données. C'est dommage, car avoir des données historiques permettrait par exemple d'entraîner des modèles prédictifs.</p>
<h3 id="toc-comment">Comment ?</h3>
<p>J'ai récolté et héberge toutes ces données (un peu plus de 5 millions de points de donnée) gratuitement et sans avoir à mettre en place une quelconque architecture compliquée. J'utilise simplement github pour héberger la donnée et <a href="https://github.com/features/actions">github actions</a> pour la récupérer automatiquement à intervalle régulier.</p>
<div><a href="https://linuxfr.org/users/lovasoa/journaux/velib-et-open-data.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/119459/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/lovasoa/journaux/velib-et-open-data#comments">ouvrir dans le navigateur</a>
</p>
lovasoahttps://linuxfr.org/nodes/119459/comments.atomtag:linuxfr.org,2005:Diary/383882019-03-06T19:04:29+01:002019-03-06T19:04:29+01:00machine learning - expérimentation foireuseLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Bonjour Nal,</p>
<p>Je dois vendre mon véhicule. Je souhaite estimer le bon prix pour le vendre : juste assez pour en tirer un bon bénéfice mais pas trop pour qu'il puisse trouver acquéreur.<br>
Je peux aller sur un site spécialisé qui moyennant quelques informations me produira cette estimation.<br>
Mais à l'heure du machine learning, avouez que ce n'est pas très palpitant.</p>
<p>Alors j'ai collecté deux cent annonces de ventes d'un véhicule de même marque, même modèle, dont j'ai extrait le kilométrage, l'année de mise en circulation et le prix de mise en vente.</p>
<p>Mon hypothèse est qu'il existe une corrélation entre ces trois caractéristiques ; et par conséquent, que l'on peut prédire le prix de vente à partir de l'année de mise en circulation et du kilométrage.<br>
Je dois donc construire et entraîner un modèle pour qu'il puisse prédire de manière fiable le prix de vente de mon véhicule.</p>
<p>Je me documente comprends que ce dont j'ai besoin est d'un algorithme de régression (versus classification) et que dans ce domaine, une des meilleurs est l'Extreme Gradient Boosting (xgboost).</p>
<p>La bonne nouvelle est qu'une implémentation existe en python. J'installe les bibliothèques logicielles nécessaires, et construit le script ad-hoc et le lance ; l'exécution est plutôt.</p>
<p>Le résultat est très décevant. L'erreur moyenne est de 700 avec des cas à plus de 1500. Pour une valeur allant de 2000 à 7000, ce n'est pas utilisable.</p>
<p>J'en déduis que soit je n'ai pas choisi le bon algorithme, soit je ne l'ai pas utilisé correctement, soit il n'y a pas de corrélation entre mes features, soit qu'il faudrait d'autres features pour améliorer la prédiction, soit que je n'ai pas assez de données d’entraînement.</p>
<p>Avez vous déjà fait des expérimentations similaires, avec plus de succès ?</p>
<div><a href="https://linuxfr.org/users/steph1978/journaux/machine-learning-experimentation-foireuse.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/116609/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/steph1978/journaux/machine-learning-experimentation-foireuse#comments">ouvrir dans le navigateur</a>
</p>
steph1978https://linuxfr.org/nodes/116609/comments.atomtag:linuxfr.org,2005:Diary/381842018-10-29T16:16:40+01:002018-10-29T16:16:40+01:00SeqTools et retour d'expérience sur le traitement de jeux de données en pythonLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-%C3%89valuation-%C3%A0-la-demande">Évaluation à la demande</a></li>
<li><a href="#toc-%C3%89valuation-asynchrone-en-arri%C3%A8re-plan">Évaluation asynchrone en arrière-plan</a></li>
</ul>
<p>SeqTools est une librairie que j'ai créée pour transformer des jeux de données trop gros pour tenir en mémoire. Elle remplit un rôle comparable à <a href="https://docs.python.org/3/library/itertools.html">itertools</a> de la librairie standard, mais fait aussi en sorte de donner accès aux éléments par indexation, ce qui est plus pratique.</p>
<p><a href="https://github.com/nlgranger/SeqTools">Dépôt du code</a><br>
<a href="https://seqtools-doc.readthedocs.io">Documentation</a></p>
<p>L'objectif principal consiste à prendre une ou plusieurs sources de données et de les combiner ou de modifier leurs éléments pour obtenir une version transformée.<br>
Par exemple, partant d'une liste de noms de fichiers d'images, on souhaite les charger, les redimensionner puis les passer en noir et blanc.</p>
<p>Pour essayer de rendre la présentation plus intéressante, je vais essayer de présenter deux aspects de la librairie qui peuvent intéresser un plus large public.</p>
<h2 id="toc-Évaluation-à-la-demande">Évaluation à la demande</h2>
<p>Mon travail, c'est l'apprentissage statistique sur des vidéos, donc il me faut un moyen simple et rapide pour définir et de tester des transformations à appliquer sur ces vidéos pour extraire des données utiles.<br>
Pour faciliter la manipulation de gros jeux de données et pour accéder rapidement à quelques valeurs, SeqTools utilise principalement l’exécution <em>à la demande</em> (=<a href="https://fr.wikipedia.org/wiki/%C3%89valuation_paresseuse">évaluation paresseuse</a> ou <a href="https://en.wikipedia.org/wiki/Lazy_evaluation">lazy evaluation</a>).<br>
Cela signifie simplement que les opérations que l'on applique sur le jeu de données ne sont appliquées que quand on réclame l'accès à un élément, et que les calculs sont fait uniquement pour l'élément souhaité en ignorant les autres.<br>
Cette approche n'est pas nouvelle, je rappelle juste ses avantages :</p>
<ul>
<li>Utilisation mémoire minimale, on ne stocke aucun résultat intermédiaire.</li>
<li>Possibilité de définir toute la chaîne de transformation rapidement…</li>
<li>… et d'accéder à n'importe quel élément (même un résultat intermédiaire) sans attendre que les calculs soit appliqués à l'ensemble des données.</li>
</ul>
<p>En pratique, ça ressemble à ça :</p>
<pre><code class="python"><span class="o">>>></span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="o">...</span> <span class="k">print</span><span class="p">(</span><span class="s2">"-> calcul"</span><span class="p">)</span>
<span class="o">...</span> <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">2</span>
<span class="o">...</span>
<span class="o">>>></span> <span class="n">a</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">m</span> <span class="o">=</span> <span class="n">seqtools</span><span class="o">.</span><span class="n">smap</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span>
<span class="o">>>></span> <span class="c1"># jusque là, f n'a pas servi, mais si on réclame un élément :</span>
<span class="o">>>></span> <span class="n">m</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="o">-></span> <span class="n">calcul</span>
<span class="mi">3</span></code></pre>
<p>Au passage, l'indexation par tranches et l'itération sont prises en charge, donc c'est assez transparent pour l'utilisateur:</p>
<pre><code class="python"><span class="o">>>></span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">m</span><span class="p">[:</span><span class="o">-</span><span class="mi">2</span><span class="p">]:</span>
<span class="o">...</span> <span class="k">print</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
<span class="o">-></span> <span class="n">calcul</span>
<span class="mi">3</span>
<span class="o">-></span> <span class="n">calcul</span>
<span class="mi">4</span></code></pre>
<p>Bien sûr il y a un inconvénient majeur : les erreurs dans les transformations ne surgissent que quand un élément est appelé, ce qui rend le débogage difficile (ex : mais où ai-je demandé cette transformation ?).<br>
Si vous vous heurtez au même problème un jour, je suggère d'utiliser la fonction <a href="https://docs.python.org/3/library/traceback.html#traceback.extract_stack">inspect.stack()</a>, qui retourne le fichier, le numéro de ligne et un extrait du code à cet emplacement pour toute la pile d'appel jusqu'à la fonction.<br>
Je m'en sers pour enregistrer l'emplacement où est créée une transformation susceptible d'échouer.<br>
En cas d'erreur plus tard, l'utilisateur reçoit un message pour l'aider à retracer l'origine du problème.</p>
<p>Plus précisément, j'utilise le mécanisme d'enchaînement d'erreurs (<code>raise ... from ...</code>) : toute erreur en provenance du code utilisateur (la fonction <code>f</code> dans l'exemple ci-dessus) est interceptée et renvoyée comme cause d'une exception générique qui contient les informations de débogage.</p>
<p>Le code simplifié ressemble à ça :</p>
<pre><code class="python"><span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="n">f</span><span class="p">(</span><span class="n">donnee</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">cause</span><span class="p">:</span> <span class="c1"># interception de l'erreur</span>
<span class="n">msg</span> <span class="o">=</span> <span class="s2">"erreur dans l'opération définie à {}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">code_qui_a_cr</span><span class="err">éé</span><span class="n">_cet_object</span><span class="p">)</span>
<span class="k">raise</span> <span class="n">EvaluationError</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">cause</span></code></pre>
<h2 id="toc-Évaluation-asynchrone-en-arrière-plan">Évaluation asynchrone en arrière-plan</h2>
<p>À un moment ou à un autre, il faut bien souvent appliquer les transformations à l'ensemble du jeu de donné, et si possible rapidement! Hélas, pour ceux qui ne sont pas familier à cet aspect de python, sachez que l'exécution concurrente et/ou sur plusieurs cœurs n'est pas son point fort. Voici donc un petit retour d'expérience.</p>
<p>Python propose deux stratégies : les threads et les processus <sup id="fnref1"><a href="#fn1">1</a></sup>. Dans les deux cas, ils nous servent à démarrer des fils d'exécution évaluent en arrière-plan les valeurs dont nous avons besoin. Les processus/threads communiquent leurs résultats de manière <em>asynchrone</em> avec le script principal qui les a démarré. Je vous passe les joyeux détails d'ordonnancement des tâches (ex : si l'élément n°5 arrive avant l'élément n°4) car cette partie ne pose pas de problème autre que la logique, ce qui nous laisse les soucis techniques :</p>
<p><em>Comment transférer les résultats de l'arrière plan vers l'utilisateur?</em></p>
<p>Pour les threads, c'est assez facile puisque le thread python a accès à l'environnement du parent, il suffit donc d'assigner le résultat dans une liste par exemple.<br>
Pour les processus, j'ai trouvé deux approches praticables :</p>
<ul>
<li>Utiliser l'objet <a href="https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.SyncManager">multiprocessing.SyncManager</a> qui ajoute une couche d'abstraction sur des queues, des verrous, etc. pour proposer un objet qui se manipule comme une liste depuis n'importe quel processus sans se poser de questions. C'est pratique et sûr vis-à-vis de la synchronisation et des accès concurrents, mais la synchronisation des données entre les processus repose sur de la sérialisation/dé-sérialisation ce qui induit un surcoût.</li>
<li>Utiliser de la <a href="https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes">mémoire partagée</a> si les objets sont des tableaux de valeurs d'un type et d'un taille donnée.</li>
</ul>
<p>J'ai utilisé la première pour ma fonction d'<a href="https://seqtools-doc.readthedocs.io/en/stable/reference.html#seqtools.prefetch">évaluation anticipée</a> et la seconde pour <a href="https://seqtools-doc.readthedocs.io/en/stable/reference.html#seqtools.load_buffers">charger des buffers</a> en mémoire.</p>
<p><em>Comment arrêter le thread/processus ?</em></p>
<p>C'est plus difficile qu'il n'y paraît! Au début, je pensais rester laxiste et laisser le collecteur de mémoire faire le ménage, ou pire laisser traîner jusqu'à la fin du script. Manque de bol, python garde une référence des threads/processus actifs <a href="https://docs.python.org/3.6/library/threading.html#threading.enumerate">dans une liste</a>, si bien que le <a href="https://stackoverflow.com/questions/49082914/python-weakref-finalize-not-run-if-background-threads-are-alive/49095183">collecteur de mémoire bloque</a> lorsqu'il fait le ménage à la fin.</p>
<p>On peut utiliser le mode <a href="https://docs.python.org/3.6/library/threading.html#threading.Thread.daemon">démon</a> pour détacher les workers de leur parent, mais j'ai eu peur de laisser des tâches zombies si le script principal plante.</p>
<p>Finalement, j'ai opté pour un système de signaux et de délais. Si un worker reste inoccupé trop longtemps, il notifie le script principal et s'arrête. Inversement, si le script parent se termine ou que l'objet qui contient les donnée est supprimé, on notifie l'arrêt. Le second cas est joliment traité par <a href="https://docs.python.org/3.6/library/weakref.html#weakref.finalize">weakref.finalize</a> qui offre un vrai mécanisme de destructeur systématiquement évalué par le ramasse-miette, contrairement à la méthode <a href="https://docs.python.org/3.6/reference/datamodel.html#object.__del__"><code>__del__</code></a> sur les objets. Si le script parent plante ou que l'utilisateur l'interrompt brutalement, c'est moins drôle : on se heurte à des erreurs non documentées sur les queues (et par conséquent avec SyncManager). D'après mon expérience, ces erreurs dérivent toutes de <code>IOError</code>.</p>
<p>Par ailleurs, il semble que les processus enfants interceptent parfois le "CTRL-C" dans le terminal. Pour diriger correctement le signal vers le parent, la solution recommandée est de lancer le sous-processus ainsi :</p>
<pre><code class="python"><span class="c1"># on désactive le support pour SIGINT (CTRL-C/KeyboardInterrupt)</span>
<span class="n">old_sig_hdl</span> <span class="o">=</span> <span class="n">signal</span><span class="o">.</span><span class="n">signal</span><span class="p">(</span><span class="n">signal</span><span class="o">.</span><span class="n">SIGINT</span><span class="p">,</span> <span class="n">signal</span><span class="o">.</span><span class="n">SIG_IGN</span><span class="p">)</span>
<span class="c1"># on lance ensuite le sous-processus qui hérite du paramètre ci-dessus</span>
<span class="n">workers</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
<span class="c1"># finalement, on restaure le support de SIGINT dans le script principal</span>
<span class="n">signal</span><span class="o">.</span><span class="n">signal</span><span class="p">(</span><span class="n">signal</span><span class="o">.</span><span class="n">SIGINT</span><span class="p">,</span> <span class="n">old_sig_hdl</span><span class="p">)</span></code></pre>
<p><em>Comment éviter que le thread/processus plante ?</em><br>
<em>Comment notifier l'utilisateur en cas d'erreur dans un thread ou un processus ?</em></p>
<p>Même en supposant que SeqTools n'a aucune erreur (ce qui est, de toute évidence, vrai ;-)), ça n'empêche pas l'utilisateur d'appliquer une opération qui va planter. L'idée, c'est d'éviter de le punir pour ces erreurs et de faciliter le débogage.</p>
<p>Pour commencer, il faut barder de <code>try ... except</code> le code qui encapsule l'exécution du code utilisateur en arrière-plan… le flot d'exécution est assez complexe à mettre eu point pour un néophyte, même si le <a href="https://github.com/nlgranger/SeqTools/blob/master/seqtools/evaluation.py#L241-L287">résultat final</a> peut sembler logique.</p>
<p>Du fait de l'exécution asynchrone, une erreur générée en appliquant une opération en arrière-plan peut survenir à n'importe quel moment. Pour faciliter la vie des utilisateurs, j'ai essayé de retarder la notification de cette erreur et de la renvoyer comme si elle venait d'arriver lorsque l'utilisateur réclame l'élément en question. Pour ce faire, mon code essaie de sérialiser l'exception soulevée et de la renvoyer au moment opportun. Il reste un petit écueil à passer : l'exception comporte la trace d'exécution qui n'est pas sérialisable. Heureusement, l'excellente bibliothèque tierce <a href="https://pypi.org/project/tblib/">tblib</a> permet de nettoyer la trace des éléments problématiques tout en gardant un maximum d'informations pour le débogage.</p>
<p>Au final, l'interface est très simplifiée pour l'utilisateur, ça plante quand il faut!</p>
<pre><code class="python"><span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">2</span>
<span class="n">donnees</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="bp">None</span><span class="p">]</span>
<span class="n">resultat</span> <span class="o">=</span> <span class="n">seqtools</span><span class="o">.</span><span class="n">smap</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">donnees</span><span class="p">)</span>
<span class="n">resultat_rapide</span> <span class="o">=</span> <span class="n">seqtools</span><span class="o">.</span><span class="n">prefetch</span><span class="p">(</span><span class="n">resultat</span><span class="p">,</span> <span class="n">nworkers</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">max_buffered</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="n">resultat_rapide</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="c1"># jusque là tout va bien, on obtient les résultats deux fois plus rapidement</span>
<span class="c1"># du moment qu'on les lit dans l'ordre.</span>
<span class="n">resultat_rapide</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="c1"># là ça renvoie une erreur qui dérive du problème de typage</span></code></pre>
<p>Voilà, ce sera tout pour ce petit retour d'expérience avec python. Si ma librairie vous intéresse, je vous suggère de regarder le <a href="https://seqtools-doc.readthedocs.io/en/stable/tutorial.html">tutoriel</a> et les <a href="https://seqtools-doc.readthedocs.io/en/stable/examples.html">exemples</a> qui donnent une idée de ses possibilités.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn1">
<p>Je laisse de côté le mécanisme futures/async/await qui vient d'arriver avec python 3.7 car il n'est pas rétro-compatible, et incompatible avec les outils dont j'ai besoin. <a href="#fnref1">↩</a></p>
</li>
</ol>
</div>
<div><a href="https://linuxfr.org/users/nlgranger/journaux/seqtools-et-retour-d-experience-sur-le-traitement-de-jeux-de-donnees-en-python.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/115585/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/nlgranger/journaux/seqtools-et-retour-d-experience-sur-le-traitement-de-jeux-de-donnees-en-python#comments">ouvrir dans le navigateur</a>
</p>
nlgrangerhttps://linuxfr.org/nodes/115585/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.atom