Thématiques principales

mardi 26 novembre 2019

SSL, TLS et OpenSSL

SSL c’est l’acronyme de Secure Socket Server [ssl]. En version 3 depuis 96 il a en fait été aujourd’hui remplacé par TLS [tls] Transport Security Layer en version 1.2 [rfc5246] depuis 2008 mais a connu une version 1.3 [rfc8446] dernièrement en 2018. Malgré cette évolution, par abus de langage, tout le monde continu d’utiliser le terme SSL.

Mais qu’est ce que SSL?

SSL/TLS est un protocole permettant la sécurisation d'échange réseau à un niveau applicatif (par opposition à une sécurisation qui aurait lieu à un niveau réseau comme avec Ipsec [Ipsec] mais on y reviendra)

Massivement utiliser pour le web en permettant la déclinaison de [http] en [https] ou [ftp] en [ftps] (attention à ne pas confondre avec sftp qui est un ftp dans ssh [sftp-ssh]), le protocole propose de compléter le protocole à sécuriser en y ajoutant ses propres échanges.

samedi 23 novembre 2019

Reseau : Iptables et réseaux docker

Maintenant que nous avons vu le principe de réécriture d’adresse [1] nous allons maintenant nous intéresser à la vraie finalité de cette suite d’article sur Iptables: comment docker réalise ses réseaux virtuels?

À coup sûr vous l’aurez compris, Iptables va avoir un rôle. Mais avant d’en arriver à cela, reprenons la construction d’un nouveau réseau virtuel docker.

Par défaut, les réseaux docker sont au nombre de 3 [2]:

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
ff147d84a29e        bridge              bridge              local
8932db1d0751        host                host                local
9a54dab98d45        none                null                local  

Ces trois types de réseau sont gérés par trois drivers spécifiques:
  • le driver null permettant d’isoler complètement les conteneurs qui serait associé à ce réseau en ne lui ne leur associant aucune adresse ip.
  • le drivers host qui permet de partager l’interface réseau de la machine hôte avec les conteneurs associé à ce réseau.
  • le driver bridge qui permet ici de construire une nouvelle interface réseau sur la machine hôte (un bridge entre autre! [3])
C’est ce dernier driver qui va nous intéresser. En effet en construisant un bridge, docker va construire une interface réseau dédiée au réseau construit spécifiquement pour l’ensemble des conteneurs qui seront déployés dans celui ci. Et afin de permettre à ces conteneurs de communiquer avec le monde extérieur, alors le bridge va servir de gateway et ce grâce …. à IpTable!

dimanche 13 octobre 2019

Reseau: Iptables: routeur logiciel

Nous revenons une nouvelle fois avec iptable mais plus pour faire du filtrage réseau mais pour du routage de trame.
Pour cela nous allons considérer l’exemple suivant: Imaginons un server sur lequel tourne un service. Ce service on le représentera par un service netcat (nc) [1]. Ce server se trouve dans le reseau privé 192.168.1.0/24. Nous, nous sommes dans un reseau externe en 192.168.0.0/24, nous ne pouvons donc pas y accéder directement. Comment allons nous nous y prendre pour que malgré tout nous puissions y parvenir?


vendredi 4 octobre 2019

Lecture: De beaux réves

Cela fait un moment que je n'avais évoqué une lecture intéressante, pas que je me soies arrêter de lire ou que je n'ai pas eut de lecture très intéressante, mais celle ci il faut l'avouer, elle sort du lot.

Alors bien sur, tout ça a  voir avec l'IA mais de façon plus subtil voire philosophique et ça tombe bien, il s'agit de cela.

De beaux rêves de Daniel C.Dennett c'est la question (vous verrez en fait il y en a deux) de la conscience, de ce qu'elle peut être, si celle ci est une illusion ou un effet de bord du fonctionnement de notre cerveau. Dans ce livre vous parlerez de divergence d'opinions sur les les Qualia, vous vous demanderai ce qu'est un Zombie Philosophique et si ça peut exister, et vous parlerez de Marie, Marie Marie mais aussi de Marie Robot!

Enfin a mon sens ce livre est un pivot sur les questions éthique de ce qu'est la conscience, de quoi elle résulte. Pour les amoureux des animaux comme des robots, c'est vraiment une œuvre a lire.

samedi 21 septembre 2019

Reseau : Iptables, firewall


Inutile d’expliquer ce qu’est un firewall? Si ?

Un firewall [firewall] est un système réseau logiciel ou matériel dont le rôle est de gérer l'accès à une machine où à un sous-réseau. Alors bien sur des firewalls, il en existe de toute sorte agissant pour divers rôles.

Le premier de ceux-ci est évidemment l'accès à un sous réseau. Il s’agit d’ici d'un système qui couplé à une passerelle permettant à deux réseaux de communiquer tout en garantissant un maximum de sécurité. Nous ne nous intéresserons pas à ce type de firewall puisque ces derniers sont généralement matériel et sont très complexe dans leur utilisation ainsi que dans leur intégration au sein d’une architecture réseau (généralement en couche).

Le second type de firewall, et qui va ici nous intéresser est le firewall logiciel dont le rôle est de sécuriser les interfaces réseaux d’un système. Par système on parle ici bien sûr tout autant d’une machine physique comme d’un PC mais aussi d’un serveur ou d’une machine virtuelle ou même encore d’un conteneur.

Dans notre exemple, nous allons utiliser Iptables, nous l'avions vu dans un article précédent [iptables]. Je vous invite a vous reporter pour faire le point.

mercredi 18 septembre 2019

Reseau: IpTables, bases


On s'éloigne un peu des précédents articles aujourd’hui en introduisant une nouvelle thématique: le réseau.

Le réseau? mais pourquoi? Simplement parce que dans la conception logicielle, la prise en compte de la composante environnementale dans laquelle le logiciel évolue est essentiel.

Cela va conditionner l’architecture, les technologies, les langages choisis et les contraintes s’appliquant à son exploitation.

Et le réseau est ce sur quoi va reposer l’essentiel de l’infrastructure système.

Par contre, je ne commencerai pas par les fondamentaux. On y viendra mais la on va tout de suite passer à un outil spécifique de la gestion réseau: Iptables.

IpTables [iptables-basics] [iptables-doc] est un outils de gestion des flux réseaux de la machine linux sur laquelle il est installé.

Par gestion, cela signifie qu’il permet d’autoriser ou d’interdire certains flux, entrant ou sortant sur certains type d’interfaces ou protocoles ou selon la provenance ou la destination. Il permet aussi de router ces même flux de façon à élaborer des politiques de gestions de l’information.

jeudi 22 août 2019

Chaines youtube intéressantes

On me demande parfois si je regarde des chaînes youtubes…. alors je ne sais pas si c’est parce que les gens ont des préjugés sur ce média où effectivement on trouve de “tout”... mais avec un peu de curiosité et d’esprit critique je pense que l’on peut vraiment y trouver son bonheur.

À vrai dire, je pense même intimement que internet et des médias tels que youtube sont  probablement l’avenir du partage de l’informations sous toutes ses formes et remplacera même à terme les médias traditionnels et les chaînes nationales (j’avoue même personnellement ne plus y trouver quoique ce soit d'intéressant….)

Ce qui manque encore aujourd'hui à ce support c’est des moyens pour canaliser l’information, mettre en évidence celle qui est pertinente et enfin la rendre accessible par différents degrés de vulgarisation.

lundi 19 août 2019

Docker Compose

Nous rebondissons une nouvelle fois! Dans l’article précédent nous avions traité d’un exemple de déploiement de python dans docker [1] . Mais nous avions constaté que nous devions spécifier dans nos images des informations concernant le contexte d'exécution, entre autre le hostname de la machine hôte… et franchement c’est un peu dégueu...

Alors jusqu’à maintenant nous avons utilisé docker dans différents exemples ou soit nous voulions expliquer certains aspects de son fonctionnement soit parce que tout simplement c'était plus simple pour  traiter le sujet en question.

À chaque fois nous n’avions eu besoin que d’un container ce qui ne posait pas de problème sauf dans ce dernier exemple!

Du coup on va voir comment s’en défaire et ça va justement être l’occasion de parler un peu de docker-compose [2].

Docker compose est justement la pour faciliter l’orchestration et le lancement de container docker dans un contexte commun cohérent [24].

vendredi 16 août 2019

Python dans docker

Nous avons dans un article précédent [1] proposer un exemple de mise en oeuvre  de deux applications python exploitant ou exposant une interface ReST.

Dans un second article [2],  nous avons également vu qu’il était possible de de packager et livrer des applications python avec divers outils comme setuptools[3] ou pyinstaller [4].

Nous avions cependant laisser une question en suspend concernant une dernière façon de déployer nos appli python. Ainsi, comme vous l’avez deviné, (le titre à tout spoiler…) nous allons dans ce présent article nous intéresser au packaging python dans des container docker.

Alors du coup après avoir lu l’article précédent, vous allez surement demander, mais pourquoi? quel est l'intérêt? et bien pour deux raisons:

  • la première est la devise de docker [5]: build once, run everywhere ce qui permet d’aller bien au delà des plateformes et des machines virtuelles
  • la seconde est que comme à la place de livrer un package ou des sources, on va livrer une image docker, alors peu importe ce que nous mettrons dans le conteneur, cela restera dans le conteneur sous notre responsabilité en minimisant complètement les interventions d’installations d’un utilisateur

La limitation est évidemment que nous ne nous intéresserons qu’aux applications basées sur le réseaux…. (WEB, CLI, etc)

Du coup pour illustrer cet article, je vous propose de reprendre l’exemple déjà traité précédemment [1] avec lequel nous proposons de déployer dans un ou plusieurs conteneur docker les différentes parties applicatives.

Pour cela nous allons donc utiliser une image de base de type alpine dans laquelle nous allons ensuite déployer ce que nous aurons décidé d’etre notre livrable. Ici, le plus simple, et de directement pousser nos sources dans l’image en s’assurant préalablement que python est bien installé ainsi que les dépendances de notre application.

On va donc définir deux Dockerfile en s’inspirant de [6]: Dockerfile_people et Dockerfile_stat respectivement pour chaque applications:


#Dockerfile_people:

FROM alpine:latest
RUN apk add --no-cache python3
RUN pip3 install setuptools Wheel
RUN pip3 install flask
COPY . /opt/
CMD cd /opt;python3 -m people



#Dockerfile_stat:

FROM alpine:latest
RUN apk add --no-cache python3
RUN pip3 install setuptools Wheel
RUN pip3 install flask python_http_client
COPY . /opt/
CMD cd /opt;python3 -m people_stat


Puis on construit nos images:


$ docker build -t people -f ./Dockerfile_people .
$ docker build -t people_stat -f ./Dockerfile_stat .


Et on les lances (en oubliant pas d’exposer leur ports d'écoutes respectifs 5001 et 5002 avec le paramètre -p)


$ docker run -it -p 5001:5001 people
$ docker run -it -p 5002:5002 people_stat


Et la ca marche! euh oui mais pas complètement… autant si on essaye d'accéder à l’interface http://localhost:5001/humans, cela fonctionne mais…. si ont tente http://localhost:5002/recall, la rien ne va plus!

Qu’est ce qui se passe?

C’est simple dans la configuration applicative réalisant les requêtes sur “recall”, nous avons dit à notre application d’aller chercher les informations sur localhost:5001/humans…. mais ce localhost…. c’est lui même! Ce que l’on voulait c'était qu'il aille sur l’autre container! Alors l’erreur semble évidente mais souvent on la fait car on développe sur un poste en local sans se préoccuper de l'aspect distribué du système que l’on est en train de faire… et donc je vous assure tout le monde tombera dans cette erreur.

Donc du coup on fait quoi?

C’est simple on ne met pas localhost mais le hostname de la machine qui expose réellement ce port! C’est à dire dans notre situation, la machine physique. (je vous laisse essayer ).

Alors bien sur généralement une information comme celle ci ne devra pas être mis en dur dans le code, on préférera s’appuyer sur des fichiers de configuration, accessibles depuis un volume ou définis par des variables d’environnement alimenté au moment du lancement du container. L’idée ici n’est pas d’aller jusque la mais déjà de voir ce que permet docker basiquement et certaines des limitations que cela induit, ici entre autre une adhérence résiduelle à la machine physique.

Nous verrons dans un prochain article comme avec docker toujours s’en affranchir.

Références:

[1] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/rest-avec-python.html
[2] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/deployer-du-python.html
[3] https://setuptools.readthedocs.io/en/latest/setuptools.html
[4] https://www.pyinstaller.org/
[5] https://uwm.edu/business/event/docker/
[6] https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xix-deployment-on-docker-containers

dimanche 11 août 2019

Déployer du Python

L’idée de cet article est de nous intéresser aux modes de livraison possible d’un programme python. L'enjeux est important car, en effet, écrire du code c’est bien mais sans livraison, c’est comme si ce code n’existait pas! Il faut donc avoir des moyens fiables et rapides permettant de fournir à un client son application python “clef en main”.

Via les sources

Dans cette approche ce qui sera fourni ce sera directement les sources qui seront mis à disposition de l’utilisateur final. Ce dernier aura alors la charge de fournir un environnement disposant non seulement de python dans la version adéquate mais aussi d’avoir pre-installer les modules dont l’application dépende.

Par exemple, sur une machine Debian, pour lancer l’application, il va être nécessaire préalablement de faire :

1
2
3
4
5
$ apt-get update && apt-get install -y --no-install-recommends python3 \
   python3-pip python3-dev \
$ apt-get clean && rm -rf /var/lib/apt/lists/
$ pip3 install --upgrade pip setuptools
$ pip3 install dependencies

Pour finalement pouvoir lancer l’application sur un appel du module :

1
$ python3 -m monAppli

Le point négatif de cet approche est la gestion des droits car lors du lancement de l’application, et à moins de préciser que l’on veille que python reste en mode interpréteur [pyt-pyc], différents fichiers vont être générés sur le poste cible. Il importe donc que ces fichiers puisse être généré sans être gêné par des permissions du systèmes mal prévu (et qui seront de toute façon délicat à mettre en oeuvre).

Via les fichiers pré-compilés

C’est une possibilité il faut l’avouer mais alors il ne sera pas possible de faire de patch ou de modification du code source sans compter que ce code sera globalement dispatcher dans un ensemble de fichiers et de répertoire (à l’image des sources).

Cela n'empêchera pas de devoir aussi préparer l’environnement d'exécution comme avec les sources. Donc c’est moins risqué coté permissions systèmes mais on perd un peu l'intérêt du python (le côté interpréteur)

À ce compte là, il serait alors mieux de simplifier la livraison en réalisant un package de l’application.

Exe, deb, rpm, etc...

Alors il est clair que faire un package de livraison, ca sera forcement en prenant en compte l’environnement de déploiement et il ne sera pas possible de déployer de la même manière sous windows où sous linux.

L'intérêt de l’approche est de fournir au client une seule référence et sera donc plus simple à gérer d’autant plus que selon la plateforme, cela permettre de bénéficier mécaniquement des avantages du mode de distribution (utilisation de aptitude et gestionnaire de paquet sous linux, pre-installation et configuration de paquets en dépendances, etc.

Pourtant les problèmes cités ci dessus seront toujours présents, code sources ou fichiers pre-compilés, il faudra gérer la dispersion de fichiers dans l’environnement.

On voit bien donc que le problème ici n’est pas le package de livraison, mais son contenu. Heureusement la communauté python à pensé à tout et à fournir différents outils pour livrer de façon cohérente notre application.

Pre-packaging

Ainsi pour réaliser ce pre-packaging, il existe quelques outils comme [setuptools], [distutils], [zipapp], [pyInstaller], py2exe (Windows) ou encore [cx_Freeze] (Windows et Linux) pour ne citer que les plus connu.

PyInstaller


Le plus simple à utiliser est PyInstaller, on l’installe et on l’applique sur le main:

1
2
$ pip install pyinstaller
$ pyinstaller monAppliv.py

et on obtient un répertoire dist dans nos sources contenant notre applications et toutes les librairies nécessaire à son exécution.

En voyant ca vous allez me dire, ok on a troqué les fichiers sources oi pre-compilé avec des librairies…. oui mais la il n’est pas nécessaire d’installer python sur le poste! Par contre attention, la construction qui à été réalisé à être spécifique à une plateforme… donc il faudra prévoir un build pyinstaller par plateforme cible.

setuptools


Setuptools est à l’inverse un package qui va permettre de construire un module versionable comme ceux télécharger lors de l’utilisation de pip install.

Setuptools repose sur distutils et je ne présenterai donc pas ce dernier qui est de plus bas niveau dans la construction des modules package python.

Setuptools est probablement l’outil le plus employé par la communauté car il permet l'intégration du module développé au sein d’un Server exploitable via pip. Ainsi même si python doit être préalablement installer sur le poste cible, la gestion des sources et fichier pre-compilés devient complètement transparent pour le développeur et le client.

De plus, contrairement au contraintes d’une gestion externe du packaging, setuptools, en gérant le versionning va faciliter via pip la mise à jour et l'évolution de l’application.

Pour cela il faut d’abord créer un descripteur de l’application et ce en python. Par exemple dans le cas de nos applications ReST [rest-py], on va créer un fichier setup.py contenant le code suivant:

1
2
3
4
5
6
7
8
from setuptools import setup, find_packages
setup(
    name="people",
    version="0.1.0",
    packages=find_packages(),
    scripts=['people.py','json_tk.py','human.py'],
    install_requires=['docutils>=0.3','Flask>=1.1.1']
)

Ensuite ce code sera exécuté bêtement comme un script :

1
$python setup.py sdist

Ce code va alors produire un targz de notre application contenant nos sources et déclarant les dépendances adéquates pour son installation.

Ainsi une fois packager il suffit alors de fournir ce package au client que celui ci exécute la commande suivante:

1
$ pip install people-0.1.0.tar.gz

Notre application est alors installée dans python (à cette occasion on préférera utiliser un virtualenv…) Et utilisable comme ceci:


1
$ python -m people

Dans cet exemple, avec setuptools, on voit que l’on est beaucoup moi adhérant à la plateforme mais que cela implique que python soit préalable installer dans celle-ci.

L’avantage sera évidemment sa mise à jour mais on notera que si ici on a créé un module setup.py, le nom de ce fichier est impératif sinon l’installation ne sera pas possible avec pip. du coup on notera que notre façon de faire nos applications dans l’exemple [rest-py] se prête finalement assez mal avec cette approche (à moins de séparer le code métier du code applicatif mais cela peut être laborieux)

Conclusions

Nous venons de passer en revue l’ensemble des manière de livrer et déployer une application python. Toutes ces façons de faire comportent leur lot d’avantages et d'inconvenants. On notera malgré tout que la communauté python n’est pas en reste de solution pour fournir des approches, il restera ensuite à la charge des développeurs de faire un choix selon le contexte et les contraintes.

Cependant, nous n’avons pas évoqué une dernière solution possible pour la livraison et le déploiement de nos applications python…. à votre avis???

Références:

[rest-py] https://un-est-tout-et-tout-est-un.blogspot.com/2019/08/rest-avec-python.html
[pyt-pyc] https://stackoverflow.com/questions/154443/how-to-avoid-pyc-files
[distutils] https://docs.python.org/fr/3/library/distutils.html
[pyinstaller] https://www.pyinstaller.org/
[zipapp] https://docs.python.org/fr/3/library/zipapp.html
[setuptools] https://setuptools.readthedocs.io/en/latest/setuptools.html
[cx-freeze] https://cx-freeze.readthedocs.io/en/latest/index.html

jeudi 8 août 2019

Docker registry

Quand on utilise docker, on en vient forcément un jour à se dire (et en fait ca arrive même très très vite), je veux publier les images que j’ai construit. Dans l’utilisation que l’on en fait quotidiennement, c’est à dire le cadre privé, l’utilisateur utilisera le docker hub.

Pourtant le docker hub est publique, c’est à dire que toutes les images que vous allez y déployer seront disponibles aux autres utilisateurs de docker.

C’est évidemment une approche qui se tient de favoriser par défaut la diffusion de images et de vouloir en faire profiter tout le monde dans un esprit de libre échange et bien sur il est quand même possible moyennant un loyer de disposer d’un espace privé dans le docker hub afin de rendre nos images accessibles qu’à un ensemble d’utilisateurs spécifiques ayant les permissions adéquates.

Pourtant, tout cela implique donc de laisser la responsabilité à docker hub de gérer l’accessibilité à nos données alors qu’en fait si le besoin de réutilisation des images est local et interne à l’entreprise, alors il devient légitime de vouloir avoir votre propre “docker hub” à la maison.

Alors sans aller sur un docker-hub avec toute l’interface graphique, ce qui importe c’est de disposer du logiciel qui va gérer nos images. Pour cela, comme le docker hub, il faut utiliser l’image registry [1] fourni par docker [2] [3].

Pour l’utiliser rien de compliqué: il suffit de lire la doc:

$ docker pull registry

Ensuite on lance un conteneur instance de cette image en exposant le port de l’API pour y accéder en prévoyant son redémarrage en l'état en montant un volume:


$ docker run -d -p 5000:5000 -v “/media/storage/registry-volume:/var/lib/registry:rslave” registry:2.7.1


Une fois le container lancer, il reste à tester la registry:

$ docker pull ubuntu
$ docker tag ubuntu localhost:5000/ubuntu
$ docker push localhost:5000/ubuntu

et c’est tout? oui pour déployer des images mais, comment les récupérer? et surtout savoir qu’elles existent?

C’est la que l’on va utiliser l’API [4] de la registry. Par exemple pour connaitre le contenu de notre registry on peut exécuter la requête ReST GET suivant:

$ curl -X GET http://localhost:5000/v2/_catalog

{"repositories":["artifactory-oss","hello-world","hw","jenkins","nginx","registry","sonarqube"]}

On obtient alors au format Json l’ensemble des images contenu dans la registry.

Pour en connaître ensuite le détail des tags existant pour une image, il va falloir élaborer une autre requête:


$ curl -X GET http://localhost:5000/v2/hello-world/tags/list

{"name":"hello-world","tags":["latest"]}


Il existe via cette API bien d’autres types de requêtes permettant de manipuler les informations contenues dans la registry et entre autre de les manipuler manuellement comment en faisant les push ou les pull avec la commande docker.

Pour en savoir plus la doc est la référence [4].

Références

[1] https://github.com/docker-library/docs/tree/master/registry
[2] https://docs.docker.com/registry/
[3] https://hub.docker.com/_/registry
[4] https://docs.docker.com/registry/spec/api/

mardi 6 août 2019

Rest avec Python


Après de nouveau un bon mois sans rien avoir poster dans le blog, voici la suite de l'article précédent sur ReST [rest-th] mais en rentrant un peu plus dans le concret.

Ainsi je vous propose de voir ce que python nous propose pour aborder cette problématique de réalisation ou d'interrogation d’une interface ReST.

Pour faire simple, nous allons considérer une population d’individu sur laquelle nous allons collecter des informations et que nous exposerons via une première interface ReST. Grâce à ce référentiel, nous pousserons un peu plus loin le travail en proposant une seconde interface ReST dont la logique ici sera d’exploiter la première interface afin de fournir divers indicateurs.



Le schema precedent présente les deux applications et leur environnement. L’idée est simple l’une expose des données génériques, l’autre les exploite pour exposer des données de plus haut niveau.

Rest-people

Cette partie de l’application est celle présentant les données à plat. Elle s’alimente sur une base de données (que l’on simplifiera par un liste par soucis de simplicité) et les exposera via deux interfaces ReST, un GET permettant de récupérer la liste complément des individu de la base et un GET permettant d’en sélectionner un seul en fonction de son Id. On complètera cette application par une méthode POST pour ajouter un élément à la population.

En python pour faire cela, on va commencer par créer des humains, des hommes et des femmes en faisant des classes python très classiques.


class Human:

    def __init__(self, id, name, age):
        self.id = id
        self.name = name
        self.age = age

    def __getitem__(self, name):
        return self.__getattribute__(name)

    def __setitem__(self, name, valeur):
        return self.__setattr__(name, valeur)

    def __delitem__(self):
        return ","


class Men(Human):

    def __init__(self, id, name, age):
        Human.__init__(self, id, name, age)


class Women(Human):

    def __init__(self, id, name, age):
        Human.__init__(self, id, name, age)


Ensuite avant de réaliser l’interface ReST, on va s'intéresser au format des données que l’on va manipuler. Ici pas de xml, mais du Json [json].

Dans python, pour manipuler du json, on utilise…. la librairie Json… c’est aussi simple et dans les faits la seule chose dont on va avoir besoin ce sont les méthodes “jsonify“ et “loads“.

  • La première nous servira à transformer un objet en json à la particularité que l’objet en paramètre doit être un dictionnaire ou une liste de dictionnaires. 
  • La seconde à l’inverse nous chargera notre json… pourtant comme le travail est fait à moitié, on va devoir compenser un peu et au passage se faciliter la vie.

En l'occurrence nous allons utiliser du code permettant de serialiser les informations d’un objet (avec les données des attributs, le nom de classe ou son module) et à l’inverse de deserialiser afin de recréer les instances. Ce code peut se trouver sur internet [json-conv].


def convert_to_dict(obj):
    """
    A function takes in a custom object and returns a dictionary representation of the object.
    This dict representation includes meta data such as the object's module and class names.
    """

    #  Populate the dictionary with object meta data
    obj_dict = {
        "__class__": obj.__class__.__name__,
        "__module__": obj.__module__
    }

    #  Populate the dictionary with object properties
    obj_dict.update(obj.__dict__)

    return obj_dict


def dict_to_obj(our_dict):
    """
    Function that takes in a dict and returns a custom object associated with the dict.
    This function makes use of the "__module__" and "__class__" metadata in the dictionary
    to know which object type to create.
    """
    if "__class__" in our_dict:
        # Pop ensures we remove metadata from the dict to leave only the instance arguments
        class_name = our_dict.pop("__class__")

        # Get the module name from the dict and import it
        module_name = our_dict.pop("__module__")

        # We use the built in __import__ function since the module name is not yet known at runtime
        module = __import__(module_name)

        # Get the class from the module
        class_ = getattr(module, class_name)

        # Use dictionary unpacking to initialize the object
        obj = class_(**our_dict)
    else:
        obj = our_dict
    return obj


Enfin maintenant que les éléments sont en place, il reste à mettre en oeuvre l’interface ReST. Pour cela on va utiliser le framework Flask [flask] qui permet comme avec Spring-WS (nous y reviendrons) de spécifier des routes associées aux verbes http et décorant des méthodes qui seront alors exécuté en fonction des chemins appelés.

Mais avant cela initialisons le contexte Flask: il nous faut des imports, un jeu de données (oui parce que dans cet exemple, on va pas non plus faire une BDD  pour 3 humains….) et définir le main qui construira le contexte Flask:


#!flask/bin/python
from flask import Flask, jsonify, request
import json

from json_tk import convert_to_dict, dict_to_obj
from human import Men, Women

people = [
    Men(1, "Bob", 12),
    Women(2, "Alice", 10)
]

app = Flask(__name__)

if __name__ == '__main__':
    app.run(debug=True, port=5001, host='0.0.0.0')


Maintenant on va définir les méthodes associées aux routes et verbes http.


@app.route('/humans', methods=['GET'])
def get_humans():
    return jsonify([convert_to_dict(human) for human in people])


@app.route('/humans/<int:human_id>', methods=['GET'])
def get_human(human_id):
    return jsonify([convert_to_dict(human) for human in people if human.id == human_id][0])


@app.route('/humans', methods=['POST'])
def post_human():
    print("REQUEST:" + str(request.data))
    json_response = json.loads(request.data)
    human = dict_to_obj(json_response)
    human.id = len(people)+1
    people.append(human)
    return jsonify(human.id)


On lance ca avec IntelliJ et on consulte les chemins avec un navigateur (http://0.0.0.0:5001/humans)


[
  {
    "__class__": "Men", 
    "__module__": "human", 
    "age": 12, 
    "id": 1, 
    "name": "Bob"
  }, 
  {
    "__class__": "Women", 
    "__module__": "human", 
    "age": 10, 
    "id": 2, 
    "name": "Alice"
  }
]


Pour le test du POST, on va utiliser Postman [postman], un outil indispensable pour tester une API ReST.

On interroge alors la liste et on voit que notre nouvel humain est bien la!


[
  {
    "__class__": "Men", 
    "__module__": "human", 
    "age": 12, 
    "id": 1, 
    "name": "Bob"
  }, 
  {
    "__class__": "Women", 
    "__module__": "human", 
    "age": 10, 
    "id": 2, 
    "name": "Alice"
  }, 
  {
    "__class__": "Men", 
    "__module__": "human", 
    "age": 24, 
    "id": 3, 
    "name": "Micka"
  }
]

ReST-people-stat

On a une première application qui fournit des données de base. La seconde doit s’interfacer sur celle-ci et disons

  • fournir la liste des humains tel quel
  • fournir la moyenne des âges des humains
Il faut un outil pour interroger la première interface. Pour cela on va utiliser le framework  python_http_client qui fournit un client http.


import json
import functools
from flask import Flask, jsonify
from python_http_client import Client

from response import str_http_response
from json_tk import dict_to_obj, convert_to_dict
from human import Moyenne

client = Client(host='http://localhost:5001/humans')
app = Flask(__name__)

if __name__ == '__main__':
    app.run(debug=True, port=5002, host='0.0.0.0')

On va ajouter une classe calculant la moyenne en utilisant la librairie functools:


import functools

class Moyenne:

    def __init__(self, humans):
        self.moyenne = (functools.reduce(lambda x, y: x.age + y.age, humans)) / len(humans)


Ensuite on va d’un côté faire un recall pour voir comment fonctionne le client.

@app.route('/recall', methods=['GET'])
def get_recall():
    response = client.get()

    str_http_response(response)

    json_response = json.loads(response.body)
    humans = [dict_to_obj(human) for human in json_response]
    print("HUMANS" + str(humans))
    return jsonify([convert_to_dict(human) for human in humans])


Et on va pouvoir implementer la methode get_moy. pour  cela on aussi utiliser un module permettant une approche map/reduce. Je vous laisse analyser le code:


@app.route('/moy', methods=['GET'])
def get_moy():
    response = client.get()
    str_http_response(response)

    json_response = json.loads(response.body)
    humans = [dict_to_obj(human) for human in json_response]
    print("HUMANS" + str(humans))

    moyenne = Moyenne(humans)
    print("MOYENNE:" + str(moyenne))
    return jsonify(moyenne.__dict__)


Il ne reste plus qu’à tester! On lance l’application people. La second ensuite, on vérifie alors la methode recall:


[
  {
    "__class__": "Men", 
    "__module__": "human", 
    "age": 12, 
    "id": 1, 
    "name": "Bob"
  }, 
  {
    "__class__": "Women", 
    "__module__": "human", 
    "age": 10, 
    "id": 2, 
    "name": "Alice"
  }
]


Puis on regarde si la moyenne fonctionne:


{
  "moyenne": 11.0
}


Pour le jeux on ajoute dynamiquement un nouvel humain dans l’application people… avec Postman et on vérifie que la moyenne à changé:


{
  "moyenne": 13.0
}


Voilà nous avons fait le tour de notre application en deux temps. Pourquoi en deux parties? ba parce que ça va être un bon prétexte de mettre ça dans des conteneurs docker! Mais ça sera pour le prochain article!

Références

[rest-th] https://un-est-tout-et-tout-est-un.blogspot.com/2019/06/rest-introduction.html
[json-conv] https://school.geekwall.in/p/ByaW0IVqN/json-the-python-way
[json] https://medium.com/python-pandemonium/json-the-python-way-91aac95d4041
[flask] https://flask.palletsprojects.com/en/1.1.x/
[rest-tuto] https://www.codementor.io/sagaragarwal94/building-a-basic-restful-api-in-python-58k02xsiq
[flask-mega] https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world
[postman] https://www.getpostman.com/

samedi 15 juin 2019

ReST : Introduction

ReST pour Representational State Transfert [wiki] est un style d’architecture formalisé par [Thomas Fielding] en 2000 dans sa thèse de doctorat. Le but de ReST est de formaliser et simplifier les échanges d’informations et la communication entre les applications sous la forme d’une API [rest-quick].

En se basant sur HTTP (v1 et maintenant v2 [http2-rest] ) Rest bénéficie d’un mode de communication simple et universel (technologiquement parlant) dont les mécanismes de sécurisations et d’attaques sont connus (ssl, tls, dos, basic authent, SAML, OAuth2, JWT, la liste est longue et nous y reviendrons ....). 

Dans le principe, l’idée est simple: exposer et fournir une ressource au travers d’HTTP. Conformément à ce protocole ceci est réalisé grâce à son URL (Uniform Resource Locator) [url-uri] qui permet au client d’identifier et de manipuler cette ressource.

Lorsque l’on parle de ressource, il va de soit que cela se conçoit dans le cadre de HTTP. Ainsi la ressource est un objet virtuel présent côté serveur dont le cycle de vie sera gérer par celui ci mais avec lequel le client aura la possibilité d’interagir en manipulant les verbe du protocole: (GET, POST, PUT, DELETE, PATCH).



De manière générale ces interactions du client avec la ressource c’est à dire les verbes HTTP pourront suivre une logique de gestion de type CRUD et ainsi voir l’interface serveur Rest comme une interface d'accès à des données comme on le ferait avec une BDD.

Ainsi, pour être plus explicite, lorsque le client réalisera une requête GET, cela pourra être vu comme une requête Read sur la ressource si celle ci était une base de donnée. Si le client réalise une requête de type de POST, cela équivaudra à une requête Create, s'il réalise un PUT, cela équivaut à un update de la ressource [put-vs-post] et s’il fait un DELETE, cela est comme on pourra s’en douter un Delete de la ressource.

Il est évident que cette vision mappant ReST et CRUD est un raccourci abusif [rest-not-crud] car une API ReST doit être spécifié et construire selon un besoin utilisateur clair et non dans une logique où l’on pourra tout faire sur le même endpoint! Ainsi si, dans le cas des modèles simples, adopter une logique CRUD ne sera probablement pas une bêtise, rapidement, il faudra penser l’API ReST comme étant une fenêtre exposant des ressources orientés d’un même modèle de données.

De même, les interactions sont l’occasion de réaliser des échanges d’informations. Ainsi lorsque le client réalise un GET sur une ressource, son but est de disposer des informations la concernant. De même lorsque qu’il réalise un POST, sont but est de pousser les informations dont il dispose afin d’enregistrer ces informations sous la forme d’une nouvelle ressource dans le server. Mais encore une fois, cela ne préjuge pas de la structure des données qui seront réellement mise en base de données, (si celle ci existe).

Comme nous l’avons déjà dit Rest s’appuie sur HTTP et c’est ainsi qu’est formalisé la manière d'échanger de l’informations, quoi? non pas en html! (bien que dans l’absolu ça ne soit pas impossible) mais plutôt en spécifiant le type MIME dans le header de la transaction.

Pour rappel, dans le protocole http, le type MIME permet au client et au server de se mettre d’accord sur le format des données qu’ils vont s'échanger. Ainsi, dans le web classique, on trouve de html, du text des images… Ici avec ReST on trouvera essentiellement de l’XML et plus probablement du JSON (nous reviendrons sur cela).

De l’XML? mais c’est comme SOAP alors! ? et ba loupé! [soap-vs-rest]

En effet Rest et Soap sont souvent comparés et traité à un même niveau sauf que s’ils sont tous deux des protocoles de communication s’appuyant sur HTTP, il est existe une différence importante entre les deux:

  • Rest tend à respecter les standard du web dans l’utilisation de http et utilise ce dernier comme un protocole applicatif où les verbe sont utilisé pour accéder convenablement aux ressources, elle même correctement formalisé selon des URL adaptée.  
  • SOAP n’utilise HTTP que comme un protocole de transport et toute la logique applicative est dans le message SOAP

Pour mieux comprendre ces différences il convient de lire l’article de Martin Flowers sur Richardson Maturity Model [richardson], [richardson-comment].

Ainsi en tant que style d’architecture, en fournissant un interface virtualisant la consommation ou la production de ressources, facilite le découplage des différentes parties applicatives du systèmes conduisant aux architectures microservices (mais cela est une autre histoire).

Ainsi ces architectures à base ReST en s'appuyant sur http impliquent (ou permettent de bénéficier) différentes propriétés qu’il convient de prendre en compte afin de se prémunir d’erreur de conception.
Entre autre:

  • ReST est un style d’architecture basé sur le modèle Client Serveur impliquant que chacun à un rôle différent, le premier consomme les informations, le second, les fournit ou les enregistre.
  • ReST est sans etat. Cela est important dans le sens où les requêtes du client sur le serveur sont indépendantes et doivent se suffirent à elle même pour satisfaire le besoin. De son côté, le serveur ne construit pas de session.
  • ReST autorise la mise en place de cache pour garantir les performances du système. Ce(s) cache(s) peuvent être implémenté(s) en tout lieu de la chaîne de commande (le client, le serveur où tout autre composant intermédiaire)
  • ReST doit fournir une interface uniforme et homogène de façon à faciliter l'accès et le parcours des données
  • ReST peut être conçu en couche autorisant l’utilisation de proxy et reverse proxy ainsi que la délégation d’appel à des sous API ReST. 

Un dernier point spécifie que ReST, optionnellement, peut fournir du code à la demande. J’admet que ce point reste pour ma part assez nébuleux et je ne l’aborderais pas tant j’ai du mal à concevoir l’utilisation de ce point, sa mise en oeuvre et les éventuelles problèmes de sécurité que cela peut poser.

Bien maintenant que l’on en à parler, ca serait bien d’y gouter aussi un peu à du ReST!

Du coup prenons le cas d’une bibliothèque présentant divers resources selon différents supports.

Alors ici on va commencer par un cas particulier, celui de la ressource unique (ou de type singleton) qui va nous permettre d’obtenir des informations sur des ressources accessibles dans l’API.

Ainsi, sur le endpoint on va trouver un premier chemin accessible tel que:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
> GET: /bibliotheque
< 200 : 
{
  nom : Bibliotheque de la mairie
  resources : 
  [
    {
       nom : Les livres
       type: Livre
       url :  /bibliotheque/livres
    },
    {
       nom : Les videos
       type: Video
       url :  /bibliotheque/videos
    }

  ] 
} 


Ici on à donc grace à cette premiere requette la connaissance des resources disposibles. Celle ci de type Livre et Video sont identifiable et accessible via une url. On notera que la nomenclature de nomage est au pluriel car comme on va le voir la ressource associée (contrairement à la bibliotheque qui est un singleton pour sa part) est multiple.

Intéressons nous aux livres:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
> GET : /bibliotheque/books
< 200 :
[
  {
    id:1
    name: “Clean Code”,
    ISBN-13: 978-0132350884,
    nbrChapitre: 17
  },
  ...
]


On obtient donc une liste dans laquelle on trouvera le livre Clean Code. A noter qu’avec cette requête, on obtient donc tous les livres! Si on veut limiter cette requete, il est alors possible de faire une requete avec un param:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
> GET : /bibliotheque/books?nbrChapitre=17
< 200 :
[
  {
    id:1
    name: “Clean Code”,
    ISBN-13: 978-0132350884,
    nbrChapitre: 17
  },
  ...
]


Dans ce cas, on obtient toujours une liste de libre mais seul ceux contenant que 17 chapitres… (bon ok comme recherche c’est pas ce qu’il y à de plus courant mais c’est pour l’exemple) De même si l’on fait la même recherche mais sur le nom, on obtient:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
> GET : /bibliotheque/books?name=Clean Code
< 200 :
[
  {
    id:1
    name: “Clean Code”,
    ISBN-13: 978-0132350884,
    nbrChapitre: 17
  },
  ...
]


Ce qui revient au même… mais on notera que l’on obtient quand même une liste… pourquoi? car il s’agit de l'équivalent de faire une recherche avec un filtre, du coup ce n’est pas faire une récupération spécifique et si l’on cherchait à récupérer que le livre Clean Code, alors on a mal penser sa requête. Ce qu’il aurait fallu faire c’est :


1
2
3
4
5
6
7
8
> GET : /bibliotheque/books/1
< 200 :
 {
    id:1
    name: “Clean Code”,
    ISBN-13: 978-0132350884,
    nbrChapitre: 17
  }


La on ne récupère qu’un élément et c’est celui identifié par son identifiant unique, l’id. (équivalent à un clef primaire)

Nous n’avons jusque là fait que des GET, et on remarque qu’il manque Clean Architecture… bon ok, ajoutons le:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
> POST : /bibliotheque/books
{
    name: “Clean Architecture”,
    ISBN-13: 978-0134494166,
    nbrChapitre: 1
}
< 201 :  /bibliotheque/books/2
 {
    id:2
    name: “Clean Code”,
    ISBN-13: 978-0132350884,
    nbrChapitre: 17
  }


Bien on note plusieurs choses: d’une part on ne post pas le livre avec un identifiant, on laisse le serveur se charger de le faire. Ainsi c’est pour cela que la réponse au POST est un code 201, nous spécifiant que la ressource à bien été créée et à cela est ajouté sa localisation (afin de savoir comment la récupérer) mais aussi la ressource elle-même.

Par contre on constate une erreur, ce livre ne contient pas qu’un chapitre mais 34… Il nous faut donc corriger cela. Le hic c’est que si l’on tente de refaire un POST, il y à des chances que l’on nous réponde que la ressource existe déjà… (je vous laisse chercher le code http associé) voir peut être qu’il nous en créer une seconde en faisant par ce biais un doublon. non ce que l’on veut c’est modifier la valeur associée à la ressource.

Bon pour cela, on pourrait faire un DELETE sur l’id et refaire le POST…. bon vous avez compris, ça fonctionnerait mais c’est lourd… non il existe des façon de faire plus efficace.

Entre il est possible d’utiliser (je vous le donne en mille) soit le verbe PUT en fournissant l'intégralité de la ressource ou alors PATCH permettant de cibler la modification à réaliser. Voyons les deux:


1
2
3
4
5
6
7
8
> PUT : /bibliotheque/books/2
{
    id:2,
    name: “Clean Architecture”,
    ISBN-13: 978-0134494166,
    nbrChapitre: 34
}
< 204 


Ici dans ce cas, le PUT va donc surcharger complètement la ressource. Seul un code 204 sera retourné pour spécifier que l'opération à été réalisée (mais qu’il n’y à pas d’information supplémentaire à attendre, en effet avec un PUT on connait déjà le localisation de la ressource sinon on aurait pas pu exécuter ce verbe)

Avec le PATCH, c’est un peu plus délicat car il faut spécifier que ce qui est modifier. On prendra garde à ce que l’id ne soit pas surcharger bêtement (à noter d’ailleur que l’id est inutile dans la ressource elle-même si le location du retour du POST est correctement fourni, le seul hic ici est que l’objet ne porte alors pas son identifiant unique, on verra que ceci est alors surtout un problème de représentation des données au sein du modèle ReST. On réfléchira à ce point lorsque ne traiterons d’exemples)

Enfin donc le PATCH: (on considérant que nous n’avons pas fait le PUT précédent)


1
2
3
4
5
> PATCH : /bibliotheque/books/2
{
    nbrChapitre: 34
}
< 204 


Voilà les grandes lignes de l’utilisation de ReST et de son design afin d’en tirer le meilleur. Maintenant il reste à le formaliser et pour cela rien de tel que quelques exemples. Du coup on tachera de faire du ReST avec quelques langages histoire de bien en montrer l'intérêt et surtout que finalement, le langage en question ne change rien aux fondamentaux et aux propriétés portées par ReST!

Références

[http2-rest] https://dzone.com/articles/benefits-of-rest-apis-with-http2?edition=352091&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=Daily%20Digest%202018-01-10
[Thomas Fielding] https://www.ics.uci.edu/~fielding/
[wiki] https://en.wikipedia.org/wiki/Representational_state_transfer
[rest-tuto] https://www.restapitutorial.com/
[soap-vs-rest] https://dzone.com/articles/differences-in-performance-apis-amp-more?edition=286955&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=dd%202017-03-31
[richardson] https://martinfowler.com/articles/richardsonMaturityModel.html
[richardson-comment] https://blog.xebia.fr/2010/06/25/rest-richardson-maturity-model/
[swagger] https://dzone.com/articles/api-first-with-swagger?edition=330491&utm_source=Daily%20Digest&utm_medium=email&utm_campaign=Daily%20Digest%202017-10-12
[put-vs-post] https://restfulapi.net/rest-put-vs-post/
[rest-not-crud] https://medium.com/@marinithiago/guys-rest-apis-are-not-databases-60db4e1120e4
[rest-quick] https://blog.nicolashachet.com/niveaux/confirme/larchitecture-rest-expliquee-en-5-regles/
[url-uri] https://www.java67.com/2013/01/difference-between-url-uri-and-urn.html

vendredi 14 juin 2019

Changement de taff!!

Et bien cela va faire presque deux mois que je n’ai rien écrit! non je n’ai pas mis de coté le blog. Mais j’avoue que ces deux derniers mois ont été très chargé. D'une part, j’ai déménagé! Encore! Ce qui en terme d’organisation pour écrire, perturbe un peu les habitudes! Et aussi j’ai changé de boulot! aussi et oui ça faisait un an que j'étais chez Capemini mais je m’ennuyais.

Du coup je suis rentré chez Norsys, je me suis laisser séduire par le dynamisme, le côté écolo, et une volonté de bien faire…  et j’ai débuté le premier avril dernier… (sans blague)

Alors je disais dans un précédent article que d’ailleurs, souvent on choisi une boite surtout pour les gens qui y travaillent et le fait que l’on veuille bosser avec eux… et bien là clairement, j'étais en plein dedans! et je regrette pas! Alors initialement, c’est surtout parce que Christophe venait lui aussi d’y rentrer (bon j’ai du coup délaissé David … ^^) mais au final je l’ai assez peu croisé et j’ai dû me confronter aux effectifs en présence….

Honnêtement, ça a quand même été un choc. J’ai vu sur ces dernières années pas mal de choses, j’ai bien sûr eut des haut et des bas, j’imagine comme tout le monde… mais je ne mesurais pas à ce point comment j’avais surtout capitalisé sur des technologies qui avaient fait leur temps. D’un coup il m’a donc fallu intégrer dans mes outils du quotidien plein de nouveaux concepts et de manière de travailler….

Alors non bien sur je ne parle pas des processus de développement, de l’agilité… mais plutôt le fait que typiquement passer de Java 8 (et encore) à Java 11 implique quelques mise à jours qui même si elles ont été réalisé à titre personnelle, cela reste qu’un débroussaillage par rapport aux besoins que l’on peut en avoir dans une utilisation quotidienne!

Alors quand même! heureusement que j’ai bien débroussaillé ces dernières années! Je ne regrette pas ces heures de rafraîchissement sur Postgres et la normalisation des SGBD-R, ni cette monté en compétence sur docker qui m'a permis d'être OP des le GO sur les projets. Cependant, changer de projet, et partir sur de nouvelles techno c’est quand même la loterie et forcément on tombe toujours sur des numéros que l’on avait pas choisi!

Du coup même en ayant fait du String sur ces 10 dernières années, j’avoue que j’ai mesuré à quel point je n'étais qu’un utilisateur de cette technologie. Celle ci est vraiment trop magique et devenir vraiment compétent sur cette technologie nécessite de passer la montagne de la stupidité (cf Dunning-Kruger)

Alors oui bien sur j’ai d’autres compétences (du moins je l'espère ^^) mais quand celles ci ne sont pas à utiliser dans le cadre d’un projet, le sentiment qui en ressort est un gros syndrome de l’imposteur!

Enfin donc tout ça pour dire que ces deux derniers mois ont été très riches, que je n’ai pas eut le temps de consolider quoique ce soit mais, que je reviens doucement mais surement avec plein de sujet en tête… (sans oublier ceux qui étaient en cours….)

Du coup en aperçu, on parlera dans les prochains mois, de

  • ReST
  • Spring Boot
  • JavaScript
  • Reactor

Peut être aussi quelques sujet sur la sécurité (Spring Security, OAuth2, JWT, etc…)
Sans oublié je le disais les sujets sur l’IA:
  • Mnist
  • Les arbres de décision
  • Les forêts aléatoire


Et bien d’autres choses comme l'héritage de table dans Postgres, SpringData, peut être un peu de bayésianisme si enfin j’arrive à me poser sur ce sujet et meme un peu de réseau!

Donc à très vite pour le prochain article!

vendredi 12 avril 2019

IA : Régression non linéaire avec scikit-learn

Dans les derniers articles nous avons vu l’astuce du noyau. Maintenant, nous allons le mettre en pratique avec un problème de régression non linéaire avec l’outil scikit learn [1].

Nous allons faire comme dans la problématique linéaire [2] , nous allons produire des données non linéaire parfaites que nous allons bruiter. Ensuite nous tâcherons d’extraire un modèles pour ces deux jeux de données et nous visualisons graphiquement comment celui ci se comporte.
Les données

Ainsi commençons par construire des données. D’un côté on va se donner un plage de données avec un pas important (ça limitera la quantité de données lors de l’apprentissage). Ensuite sur la base de ce résultat, on va générer une valeur aléatoire que l’on ajoutera où on retranchera à nos précédents résultats:


print(__doc__)
import numpy as np
from sklearn.svm import SVR
import matplotlib.pyplot as plt
import math

A=0.02
B=-12
C=51
D=2*math.pow(10,8)


X=np.arange(-1000,1000,35)
SIZE=np.size(X)
print(SIZE)
Y=(A*X**3+B*X**2+C*X+D)*0.00000001
Bruit=Y + np.random.randint(0,15, size=(1,SIZE))[0]*0.005*(np.random.rand(1,SIZE)-0.5)[0]


fig = plt.figure(1,figsize=(8,8))
plt.plot(X,Y,"bo")# model lineaire
plt.plot(X,Bruit,"r.")# model lineaire bruité
plt.show()

Si l’on visualise ces données, on obtient le graphe suivant (au passage on calcule l'écart moyen au carré du résultat de la prédiction avec la vraie données initiale):


Modèles

Ensuite on construit un modèle polynomial basé sur un degré 3 (ok on triche un peu on sait que nos données sont de degré 3), au passage on se construira un modèle linéaire histoire de comparer un peu.


svr_lin = SVR(kernel='linear', C=100, gamma='scale')
svr_poly = SVR(kernel='poly', C=100, gamma='scale', degree=3, epsilon=.001, coef0=2)
X_=X.reshape(-1, 1)

On va alors d’abord vérifier sur nos données non bruités ce que nos modèles sont capable de faire:


svrs = [svr_lin,svr_poly]
model_color = ['c', 'g']

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 8), sharey=True)
for index, svr in enumerate(svrs):
    PREDICT=svr.fit(X_, Y).predict(X_)
    axes[index].plot(X_, PREDICT, color=model_color[index], lw=2)
    # on affiche les points supports du SVM
    axes[index].scatter(X_[svr.support_], Y[svr.support_], facecolor="none",edgecolor=model_color[index])
    # les autres points qui ne sont pas les supports (d'ou le diff)
    axes[index].scatter(X_[np.setdiff1d(np.arange(len(X)), svr.support_)],Y[np.setdiff1d(np.arange(len(X)),
        svr.support_)],facecolor="none", edgecolor="k")
    print("eccart carré moyen : {}".format(np.square(Y-PREDICT).mean()))

plt.show()

On obtient un eccart carré moyen : 6.847129083752741e-07



On voit donc que nos données sont correctement décrits mais en soit c’est normal! Mais essayons sur les données bruitées?



svrs = [ svr_lin,svr_poly]
model_color = ['c', 'g']

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16,8), sharey=True)
for index, svr in enumerate(svrs):
    PREDICT=svr.fit(X_, Bruit).predict(X_)
    axes[index].plot(X_, PREDICT, color=model_color[index], lw=2)
    # on affiche les points supports du SVM
    axes[index].scatter(X_[svr.support_], Bruit[svr.support_], facecolor="none",edgecolor=model_color[index])
    # les autres points qui ne sont pas les supports (d'ou le diff)
    axes[index].scatter(X_[np.setdiff1d(np.arange(len(X)), svr.support_)],Bruit[np.setdiff1d(np.arange(len(X)),
        svr.support_)],facecolor="none", edgecolor="k")
    print("eccart carré moyen : {}".format(np.square(Y-PREDICT).mean()))


plt.show()

On obtient eccart carré moyen : 3.2041407166403025e-06

Donc la on voit que malgré le bruit, on obtient une prédiction qui colle à ce qu'étaient nos données non bruité!

Sur-modèle

Alors on pourrait se dire que si l’on cherche à augmenter le degré on pourrait peut être espérer augmenter la qualité de notre modèle… sauf que ….


svrs = [ svr_lin,svr_poly]
model_color = ['c', 'g']

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16,8), sharey=True)
for index, svr in enumerate(svrs):
    PREDICT=svr.fit(X_, Bruit).predict(X_)
    axes[index].plot(X_, PREDICT, color=model_color[index], lw=2)
    # on affiche les points supports du SVM
    axes[index].scatter(X_[svr.support_], Bruit[svr.support_], facecolor="none",edgecolor=model_color[index])
    # les autres points qui ne sont pas les supports (d'ou le diff)
    axes[index].scatter(X_[np.setdiff1d(np.arange(len(X)), svr.support_)],Bruit[np.setdiff1d(np.arange(len(X)),
        svr.support_)],facecolor="none", edgecolor="k")
    print("eccart carré moyen : {}".format(np.square(Y-PREDICT).mean()))


plt.show()

On obtient eccart carré moyen : 0.002954915450864114


Ce que l’on constate ici est ce que l’on appelle de l’overfiting, c’est à dire un sur-apprentissage, cela arrive lorsque l’on pousse le modèle à vouloir trop coller au données. Au lieu d'être une bonne approximation de l’ensemble des données, le modèle devient  une surreprésentation de celles ci et sera incapable d’en prédire de nouvelles.

Conclusion

Pour conclure la-dessus je ferai surtout un point sur notre avancement dans l’IA. Ainsi on peut remarquer que la nous sommes sur la fin des régressions non linéaires (et les linéaires). Bien sur on ne dira pas que l’on en a fait le tour car il serait encore possible de traiter aussi des algo de type lasso, ridge, ou même elastic-net cependant dans le principe, ces approches restent proches de ce que l’on pu voir jusque maintenant (de plus la littérature sur ces modèles de régression est plus que riche et vous laisserai donc chercher).

De même nous avons vu des classifications binaires mais aussi des classifications multi-étiquettes (souvenez vous du dataset iris) et nous avons la aussi fait un peu le tour! Nous nous attacherons quand même à conclure sur une problématique classique du domaine qu’est Mnist car ce problème est tellement un classique qu’il ne serait pas normal de ne pas en parler.

Mais du coup ça y est on a fini avec l’IA? bien sur que non… il reste encore plein de sujet!

Rassurez vous!! c’est loin d'être fini! Sans vouloir trop entrer dans ces sujets ou spoiler, le machine learning compte encore d’autres approches comme les arbres de décisions ou les forêts aléatoire et bien sur, après cela, les réseaux de neurones (ça y est on y arrive!!).

Cependant, il faudra un peu de patience… car d’autres sujets surprises vont s’intercaler entre tout ça…. enfin vous verrez!

Références