Un exemple de mauvais caviardage

Un exemple de mauvais caviardage

Dans l'un des cas que j'ai traité récemment, j'ai été confronté à un document pdf dans lequel une partie du texte avait été caviardée. Voici ce à quoi le document ressemblait :

caviardage.

Les métadonnées indiquaient que le fichier était issu de Word. Il semblerait donc que la personne en charge du caviardage avait simplement ajouté des carrés noirs au-dessus du texte qu'il voulait cacher, avant d'enregistrer le fichier en pdf directement avec Word.

Ça n'est pas du tout la bonne manière de procéder. En effet, il suffit d'ouvrir le document avec Inkscape pour voir que les carrés noirs sont des objets :

inkscape

Pour voir le texte dans son intégralité, il suffit de supprimer ces objets :

inkscape

Selon moi, pour vraiment cacher une partie du contenu, il aurait fallu deux étapes de plus. Dans un premier temps, il aurait fallu convertir le pdf dans un format de fichier réellement graphique (png ou jpg par exemple). Et dans un second temps, il aurait fallu le reconvertir en pdf.

Dataviz des prénoms masculins les plus donnés en France

J'ai fait une petite vidéo qui montre les prénoms masculins les plus donnés en France par année (de 1900 à 2017) et par département.

Je me suis appuyé sur les données de l'Insee et sur cette carte.

Voici rapidement les étapes du processus :

  • Modification de la carte pour :
    • la simplifier en supprimant tout ce qui ne m'était pas utile,
    • qu'elle intègre les prénoms,
    • qu'elle puisse être facilement modifiable,
    • qu'elle tienne compte des départements avant 1968,
  • Intégration des données dans une base de données sqlite pour qu'elles soient facilement interrogeables en python,
  • Création d'un script en python qui :
    • attribue une couleur à chaque prénom,
    • interroge la base de données,
    • pour chaque année, modifie et enregistre le fichier svg
  • Conversion des fichiers svg en png,
  • Création de la vidéo avec iMovie.

Et voilà.

Matrice 8x8 sans fil avec un teensy et un ESP8266

Matrice 8x8 sans fil avec un teensy et un ESP8266

Il y a quelques années, j'avais acheté un teensy et une matrice 8x8. J'avais également quelques ESP8266 et un module AMS1117. Tout cela devait me permettre de créer un afficheur sans fil contrôlable depuis un navigateur.

J'ai sorti mon fer à souder et j'ai assemblé le tout de la manière suivante : teensy wiring

Il ne restait plus qu'à coder mon idée d'afficheur sans fil dans l'IDE arduino.

C'est ce que j'ai fait en utilisant le phénomène de la persistance rétinienne. Il n'y a, en effet, jamais plus d'une led allumée à la fois, mais elles changent tellement rapidement que l'œil ne voit pas de clignotement :

void matrix_disp(int x, int y) {
  digitalWrite(row[x], HIGH); // on
  digitalWrite(col[y], LOW);
  digitalWrite(col[y], HIGH); // off
  digitalWrite(row[x], LOW);
}
void matrix_draw(bool m[8][8]) {
  elapsedMillis time;
  while (time < timer) {
    for (int x = 0; x < 8; x++) {
      for (int y = 0; y < 8; y++) {
        if (m[x][y] == true) matrix_disp(x, y);
      }
    }
  }
}

Après avoir réussi à faire une interface web (minimaliste) et à faire défiler un texte sur la matrice, j'ai ajouté la possibilité d'afficher l'heure, grâce au protocole NTP :

time_t ntp_gettime() {
  if (TIMEDEBUG == 1) DEBUG_SERIAL.println("ntp_gettime");
  unsigned long timestamp = 0;
  byte ntp_request[48];
  for (int i = 0; i < 48; i++) {
    ntp_request[i] = 0x00;
  }
  ntp_request[0] = 0xE3;   // LI, Version, Mode
  ntp_request[1] = 0;      // Stratum, or type of clock
  ntp_request[2] = 10;     // Polling Interval
  ntp_request[3] = 0xEC;   // Peer Clock Precision
  wifi_buffclr();
  WIFI_SERIAL.println("AT+CIPSTART=1,\"UDP\",\"fr.pool.ntp.org\",123,123");
  delay(1);
  if (WIFI_SERIAL.find("OK")) {
    wifi_buffclr();
    WIFI_SERIAL.println("AT+CIPSEND=1,48");
    delay(1);
    if (WIFI_SERIAL.find(">")) {
      wifi_buffclr();
      for (int i = 0; i < 48; i++) {
        WIFI_SERIAL.print((char)ntp_request[i]);
      }
      delay(1);
      if (WIFI_SERIAL.find("SEND OK")) {
        wifi_buffclr();
        delay(1);
        if (WIFI_SERIAL.find("+IPD,1,48:")) {
          byte result[4] = {0, 0, 0, 0};
          byte nul[32];
          WIFI_SERIAL.readBytes(nul, 32);
          WIFI_SERIAL.readBytes(result, 4);
          timestamp = 0;
          timestamp += result[0] << 24;
          timestamp += result[1] << 16;
          timestamp += result[2] << 8;
          timestamp += result[3];
          timestamp -= 2208988800; // adjusting for epoch
          timestamp += 7200;       // adjusting for GMT + 2
          timestamp += 9;          // adjusting for display delay
          if (TIMEDEBUG == 1) DEBUG_SERIAL.println(timestamp);
        }
      }
    }
  }
  wifi_buffclr();
  WIFI_SERIAL.println("AT+CIPCLOSE=1");
  delay(250);
  wifi_buffclr();
  return timestamp;
}

mais aussi le cours du bitcoin, grace à l'API coindesk :

String btc_getprice() {
  if (BTCDEBUG == 1 ) DEBUG_SERIAL.println("btc_getprice");
  String price = "0";
  String get = "GET /v1/bpi/currentprice.json HTTP/1.1\r\nHost: api.coindesk.com\r\nConnection: close\r\n";
  wifi_buffclr();
  WIFI_SERIAL.println("AT+CIPSTART=2,\"TCP\",\"api.coindesk.com\",80");
  delay(1);
  if (WIFI_SERIAL.find("OK")) {
    wifi_buffclr();
    WIFI_SERIAL.println("AT+CIPSEND=2,85");
    delay(1);
    if (WIFI_SERIAL.find(">")) {
      wifi_buffclr();
      WIFI_SERIAL.println(get);
      delay(1);
      if (WIFI_SERIAL.find("SEND OK")) {
        wifi_buffclr();
        if (WIFI_SERIAL.find("+IPD,2,") and WIFI_SERIAL.find("EUR") and WIFI_SERIAL.find("rate_float\":")) {
          price = WIFI_SERIAL.readString();
          price = price.substring(0, price.indexOf("}"));
          if (BTCDEBUG == 1 ) DEBUG_SERIAL.println("price : " + price);
          wifi_buffclr();
        }
      }
    }
  }
  WIFI_SERIAL.println("AT+CIPCLOSE=2");
  delay(250);
  wifi_buffclr();
  return price;
}

À partir de là, on peut afficher ce qu'on souhaite...

Au final, j'ai environ 1550 lignes de codes, dont l'essentiel (~1000) pour la police 5x8 :

const bool alpha[][8][5] = {
  { { 0, 0, 0, 0, 0, }, // SP 32
    { 0, 0, 0, 0, 0, },
    { 0, 0, 0, 0, 0, },
    { 0, 0, 0, 0, 0, },
    { 0, 0, 0, 0, 0, },
    { 0, 0, 0, 0, 0, },
    { 0, 0, 0, 0, 0, },
    { 0, 0, 0, 0, 0, }
  },
[...]
  { { 0, 0, 0, 0, 0, }, // a 97
    { 0, 0, 0, 0, 0, },
    { 1, 1, 1, 0, 0, },
    { 0, 0, 0, 1, 0, },
    { 0, 1, 1, 1, 0, },
    { 1, 0, 0, 1, 0, },
    { 0, 1, 1, 0, 0, },
    { 0, 0, 0, 0, 0, }
  },
  { { 1, 0, 0, 0, 0, }, // b 98
    { 1, 0, 0, 0, 0, },
    { 1, 1, 1, 0, 0, },
    { 1, 0, 0, 1, 0, },
    { 1, 0, 0, 1, 0, },
    { 1, 0, 0, 1, 0, },
    { 1, 1, 1, 0, 0, },
    { 0, 0, 0, 0, 0, }
  },
[...]
}

C'était un projet assez fun à réaliser.

Je peux maintenant dire quand il ne faut pas me déranger quand je travaille :

Mes sources sont sur github.

Machine learning avec les API prescience d'OVH et les chiffres du MNIST

Machine learning avec les API prescience d'OVH et les chiffres du MNIST

Suite à mon article intitulé Machine learning avec OVH prescience en quelques clics, je voulais tenter le même genre de choses avec les chiffres du MNIST. À l'époque, ça n'était pas possible. En effet, le nombre de colonnes était limité à 100. Aujourd'hui, ça n'est plus le cas, la limite étant maintenant de 1000 colonnes.

Pour rappel, les chiffres du MNIST est une base de données de chiffres écrits à la main. Comme iris, c'est un jeu de données très utilisé en apprentissage automatique. Elle regroupe 60000 images d'apprentissage et 10000 images de test, issues d'une base de données antérieure.

Pour me rapprocher d'un cas réel, je voulais travailler à partir des images plutôt qu'à partir du format de la base de données originale. On trouve les images au format png sur Internet, notamment ici. On obtient l'arborescence suivante :

|____testing
| |____0
| | |____[980 fichiers png]
| |____1
| | |____[1135 fichiers png]
| |____2
| | |____[1032 fichiers png]
| |____3
| | |____[1010 fichiers png]
| |____4
| | |____[982 fichiers png]
| |____5
| | |____[892 fichiers png]
| |____6
| | |____[958 fichiers png]
| |____7
| | |____[1028 fichiers png]
| |____8
| | |____[974 fichiers png]
| |____9
| | |____[1009 fichiers png]
|____training
  |____0
  | |____[5923 fichiers png]
  |____1
  | |____[6742 fichiers png]
  |____2
  | |____[5958 fichiers png]
  |____3
  | |____[6131 fichiers png]
  |____4
  | |____[5842 fichiers png]
  |____5
  | |____[5421 fichiers png]
  |____6
  | |____[5918 fichiers png]
  |____7
  | |____[6265 fichiers png]
  |____8
  | |____[5851 fichiers png]
  |____9
    |____[1009 fichiers png]

En partant des images, il faut trouver un moyen de transformer des png en fichiers csv. Les images font 28 pixels par 28 pixel et sont en niveau de gris. Il suffit d'enregistrer la valeur du niveau de gris des 784 pixels dans un fichier texte. Un petit script rapidement écrit en python fera l'affaire :

import os
from PIL import Image

txt = "label,"
for j in range (1,785):
    txt = txt + "p" + str(j) + ","
print txt[:-1]
for i in range (0,10):
    path = "./training/" + str(i)
    files = os.listdir(path)
    for f in files:
        im = Image.open(path+ "/" + f)
        txt = str(i) + ","
        for p in list(im.getdata()):
            txt = txt + str(p) + ","
        print txt[:-1]

On l'exécute pour obtenir un fichier csv à partir duquel on pourra travailler. Il contient 60001 lignes.

$ python convert.py > num.csv
$ head -n 3 num.csv && tail -n 2 -f num.csv
label,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10, [...] ,p784
0,0,0,0,0,0,0,0,0,0,0, [...] ,0
0,0,0,0,0,0,0,0,0,0,0, [...] ,0
[...]
9,0,0,0,0,0,0,0,0,0,0, [...] ,0
9,0,0,0,0,0,0,0,0,0,0, [...] ,0

On peut maintenant envoyer notre fichier sur les serveurs OVH et les faire travailler dessus.

$ # <---------- Envoi du fichier ----------> 
$
$ cat parse.json
{"type":"CSV","headers":true,"separator":"comma","source_id":"num"}
$ curl -H "Authorization: Bearer xxx" -v -F input='@parse.json;type=application/json' -F input-file-1=@num.csv https://prescience-api.ai.ovh.net/ml/upload/source
*   Trying 51.68.117.117...
* TCP_NODELAY set
* Connected to prescience-api.ai.ovh.net (51.68.117.117) port 443 (#0)
[...]
*  SSL certificate verify ok.
> POST /ml/upload/source HTTP/1.1
> Host: prescience-api.ai.ovh.net
> User-Agent: curl/7.52.1
> Accept: */*
> Authorization: Bearer xxx
> Content-Length: 109580237
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------5eff60e8cafb6321
>
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Content-Length: 659
< Content-Type: application/json
< Date: Fri, 15 Feb 2019 13:32:49 GMT
< X-IPLB-Instance: 24883
<
* Curl_http_done: called premature == 0
* Connection #0 to host prescience-api.ai.ovh.net left intact
{"uuid":"3e42c980-1957-4239-9301-828db9487257","status":"PENDING" [...]
$
$ # <---------- Vérification de l'envoi ---------->
$
$ curl -H "Authorization: Bearer xxx" https://prescience-api.ai.ovh.net/source/num
{"uuid":"75d03427-29d9-4606-8e20-349488ac38b2","status":"BUILT", [...]
$
$ # <---------- Préprocess ---------->
$
$ cat preprocess.json
{"nb_fold":10,"problem_type":"classification","multiclass":false,"label_id":"label","dataset_id":"dataset-num"}
$ curl -H "Authorization: Bearer xxx" -H "Content-Type:application/json" -X POST https://prescience-api.ai.ovh.net/ml/preprocess/num --data-binary "@preprocess.json"
{"uuid":"23d9a5a3-f20d-4f78-bc96-b5bf4c6533e5","status":"PENDING [...]
$
$ # <---------- Vérification du préprocess ---------->
$
$ curl -H "Authorization: Bearer xxx" https://prescience-api.ai.ovh.net/dataset/dataset-num
{"uuid":"067d0c99-df40-4232-97ce-c3fae196a12c", [...] "status":"BUILT" [...]
$
$ # <---------- Optimisation ---------->
$
$ cat optimize.json
{"scoring_metric": "accuracy","budget": 6}
$ curl -H "Authorization: Bearer xxx" -H "Content-Type:application/json" -X POST https://prescience-api.ai.ovh.net/ml/optimize/dataset-num --data-binary "@optimize.json"
{"uuid":"da274781-8360-4df1-8ee9-512b4f5386a4","status":"PENDING" [...]
$
$ # ~4/5h plus tard
$ # <---------- Vérification de l'optimisation ---------->
$
$ curl -H "Authorization: Bearer xxx" https://prescience-api.ai.ovh.net/optimization?dataset_id=dataset-num
{"metadata":[...] "status":"BUILT" [...]
$
$ # <---------- Récupération des évaluations ---------->
$
$ curl -H "Authorization: Bearer xxx" https://prescience-api.ai.ovh.net/evaluation-result?dataset_id=dataset-num
{"metadata":{"page_number":1,"total_pages":1,"elements_on_page":6,"elements_total":6,"elements_type":"EvaluationResult"},"content  [...]
$
$ # <---------- Entrainement ---------->
$ # (uuid avec l'accuracy la plus faible)
$
$ curl -H "Authorization: Bearer xxx" -H "Content-Type:application/json" -X POST https://prescience-api.ai.ovh.net/ml/train/?model_id=num\&evaluation_uuid=f7493667-62c8-4c71-b32d-487fabd008f4
{"uuid":"91058b78-a7b8-4a64-9a2a-241a05906d86","status":"PENDING" [...]
$
$ # ~30 min plus tard
$ # <---------- Vérification du modèle ---------->
$
$ curl -H "Authorization: Bearer xxx" https://prescience-api.ai.ovh.net/model/num
{"uuid":  [...] "status":"BUILT" [...]

On peut ensuite interroger le modèle avec des données contenues dans le dossier testing.

$ curl -X POST "https://prescience-serving.ai.ovh.net/eval/num/transform-model" -H "Authorization: Bearer xxx" -H "Content-Type: application/json" -d '{"arguments":{"p1":"0","p2":"0","p3":"0","p4":"0","p5":"0","p6":"0","p7":"0","p8":"0","p9":"0","p10":"0", [...] "p784":"0"}}'
[...] "result":{"label":0,"probability(0)":0.99999285,"probability(1)":8.090802E-8,"probability(2)":1.6754373E-6,"probability(3)":1.0818261E-6,"probability(4)":7.6129476E-7,"probability(5)":6.4200015E-7,"probability(6)":2.0949409E-7,"probability(7)":1.4968518E-6,"probability(8)":8.1384445E-7,"probability(9)":3.089347E-7}}

Pour valider que le système fonctionne bien, il faut tester tous les fichiers contenus dans les sous dossiers de testing. Une nouvelle fois, un petit script rapidement écrit en python fera l’affaire :

import os
import json
from PIL import Image

req = 'curl -s -X POST "https://prescience-serving.ai.ovh.net/eval/num/transform-model" '
req = req + '-H "Authorization: Bearer xxx" '
req = req + '-H "Content-Type: application/json" '
req = req + '-d '
req = req + "'"
req = req + '{"arguments":{'

for i in range (0,10):
    path = "./testing/" + str(i)
    files = os.listdir(path)
    print "label " + str(i)
    ok = 0
    ok97 = 0
    tot = 0
    for f in files:
        im = Image.open(path+ "/" + f)
        j = 1
        tot = tot + 1
        req1 = req
        for p in list(im.getdata()):
            req1 = req1 + '"p'+ str(j) + '":"' + str(p) + '",'
            j = j + 1
        req1 = req1[:-1] + "}}'"
        res = json.loads(os.popen(req1).read())
        if (res['result']['label'] == i):
            if (res['result']['probability('+str(i)+')'] > 0.97):
                ok = ok + 1
            else:
                print " |__ " + f + " ~~> " + str(res['result']['label']),
                print "(prob(" + str(i) + ") = " +  str(res['result']['probability('+str(i)+')'])+ ")"
                ok97 = ok97 + 1
        else : 
            print " |__ " + f + " --> " + str(res['result']['label']),
            print "(prob(" + str(i) + ") = " +  str(res['result']['probability('+str(i)+')']),
            print "/ prob(" + str(res['result']['label']) + ") =",
            print str(res['result']['probability('+str(res['result']['label'])+')']) + ")"
    print " ==> " + str(ok) + "+" + str(ok97) + "/" + str(tot) + " fichiers identifies correctement"

Puisque mon script n'est vraiment pas efficace, il faut attendre quelques heures pour traiter les 10000 images, mais voici (une partie) des résultats obtenus :

$ python check.py
label 0
[...]
 |__ 4065.png --> 9 (prob(0) = 0.020663416 / prob(9) = 0.5178577)
 |__ 4477.png (0.9034158)
 |__ 3640.png (0.95766705)
 |__ 9634.png --> 8 (prob(0) = 0.012318781 / prob(8) = 0.62124056)
[...]
 ==> 950+20/980 fichiers identifies correctement
label 1
 |__ 956.png (0.5512372)
 |__ 8376.png (0.65549505)
 |__ 4201.png --> 7 (prob(1) = 0.1168875 / prob(7) = 0.83713335)
 |__ 5457.png --> 0 (prob(1) = 0.012380271 / prob(0) = 0.44013467)
[...]
 ==> 1108+18/1135 fichiers identifies correctement
 label 2
[...]
 |__ 2462.png (0.4456795)
 |__ 4205.png (0.92711586)
 |__ 4615.png --> 4 (prob(2) = 0.36458197 / prob(4) = 0.6208826)
 |__ 2098.png --> 0 (prob(2) = 0.42372745 / prob(0) = 0.57605886)
[...]
 ==> 926+80/1032 fichiers identifies correctement
 label 3
 |__ 7905.png (0.8120583)
 |__ 63.png --> 2 (prob(3) = 0.40702614 / prob(2) = 0.58500034)
 |__ 2921.png --> 2 (prob(3) = 0.050211266 / prob(2) = 0.79167867)
 |__ 4808.png (0.8406261)
[...]
 ==> 903+85/1010 fichiers identifies correctement
label 4
 |__ 610.png (0.9402425)
 |__ 4438.png (0.8106081)
 |__ 5068.png (0.81054324)
 |__ 1178.png --> 0 (prob(4) = 0.35439998 / prob(0) = 0.42516744)
 |__ 1634.png (0.8933352)
[...]
 ==> 887+72/982 fichiers identifies correctement
 label 5
[...]
 |__ 8160.png (0.89900964)
 |__ 1393.png --> 7 (prob(5) = 0.2399334 / prob(7) = 0.5787523)
 |__ 1378.png --> 6 (prob(5) = 0.3978061 / prob(6) = 0.42883852)
 |__ 4360.png (0.6662907)
[...]
 ==> 783+86/892 fichiers identifies correctement
 label 6
 |__ 4798.png (0.7755983)
 |__ 4571.png --> 8 (prob(6) = 0.028769718 / prob(8) = 0.88183796)
 |__ 3550.png (0.94643056)
 |__ 1569.png (0.9389341)
[...]
 ==> 893+44/958 fichiers identifies correctement
label 7
[...]
 |__ 2507.png (0.92112225)
 |__ 1754.png --> 2 (prob(7) = 0.3992076 / prob(2) = 0.5211325)
 |__ 1543.png (0.96900773)
 |__ 6576.png (0.64587057)
 |__ 1194.png --> 9 (prob(7) = 0.47039503 / prob(9) = 0.52659583)
 [...]
 ==> 911+91/1028 fichiers identifies correctement
 label 8
 |__ 9280.png (0.78978646)
 |__ 2896.png --> 0 (prob(8) = 0.021465547 / prob(0) = 0.96403486)
 |__ 8410.png --> 6 (prob(8) = 0.37426692 / prob(6) = 0.5977813)
 |__ 6603.png (0.94607025)
[...]
 ==> 872+80/974 fichiers identifies correctement
 label 9
[...]
 |__ 1554.png (0.9546513)
 |__ 1232.png --> 4 (prob(9) = 0.04343883 / prob(4) = 0.74937975)
 |__ 2129.png --> 2 (prob(9) = 0.05706393 / prob(2) = 0.53022295)
 |__ 9530.png (0.9462949)
[...]
 ==> 881+96/1009 fichiers identifies correctement

Sous forme de matrice de confusion, les choses sont plus lisibles :

matrice

  • En vert : lorsque la réponse est correcte et la probabilité supérieure à 97 %,
  • En orange clair : lorsque la réponse est correcte et la probabilité inférieure à 97 %,
  • En orange foncé : lorsque la réponse est incorrecte et la probabilité inférieure à 97 %,
  • En rouge : lorsque la réponse est incorrecte et la probabilité supérieure à 97 %,

Ainsi, les résultats sont plutôt satisfaisants. En effet, dans près de 98 % des cas, la réponse est correcte et dans seulement 0,22 % des cas, elle est incorrecte avec une probabilité très élevée. Lorsque l'on sait que le taux d'erreur moyen d'un être humain est de l'ordre de 0,5 % à 1 % (cf. ici), on constate que la machine est au moins aussi bonne que nous, pour ne pas dire meilleure.

J'avais testé, dans l'interface web mise à disposition, le machine leaning d'OVH avec un exemple simple. Le principe est le même que l'on utilise l'interface web ou l'API et des requêtes curl, comme je l'ai fait ici. Il s'agit ici d'un exemple beaucoup plus complexe. Malgré cela, les résultats sont satisfaisants.

Encore une fois, merci OVH.

Manipulation de miniatures de fichiers jpg

Manipulation de miniatures de fichiers jpg

J'avais envie de jouer avec les miniatures intégrées aux photos au format jpg pour voir comment les différents OS réagissaient...

J'ai donc pris deux photos avec mon smartphone. Il s'agit d'une feuille blanche sur laquelle j'ai écrit une fois "petite" et l'autre fois "grande" :

petite et grande

J'ai donc deux fichiers jpg contenant chacun une miniature. J'appellerai l'image sur laquelle j'ai écrit "petite" la petite et celle sur laquelle j'ai écrit "grande" la grande. L'idée est de prendre la miniature de la petite pour la mettre dans la grande.

Muni d'un bon éditeur hexadécimal, j'avais trois parties à modifier :

  • La taille du marqueur APP1 à l'offset 0x4, qui correspond à la taille des métadonnées Exif,
  • La taille de la miniature, dans ce cas à l'offset 0x4B8,
  • le contenu de la miniature, dans ce cas à l'offset 0x4D0

grande hexadecimal

Dans la grande image, la miniature fait 6153 octets (0x1809). Dans la petite image, la miniature est légèrement plus grande. Elle fait 6418 octets (0x1912). Ainsi, j'ai effectué les manipulations suivantes :

  • suppression de la miniature entre l'offset 0x4D0 et 0x1CD8 pour la remplacer par l'autre miniature,
  • remplacement de la taille de la miniature à l'offset 0x4B8 : 0x1809 (6153) devient 0x1912 (6418) et on écrit donc 0x1219 (little endian)
  • remplacement de la taille du marqueur APP1 à l'offset 0x4 : on a ajouté 265 octets au fichiers et donc 0x1CD5 (7381) devient 0x1DDE (7646) et on écrit donc 0x1DDE (big endian)

On obtient donc ceci :

grande modifiée hexadecimal

L'idée est ensuite de voir comment les systèmes d'exploitations se comportent avec une telle image.

En ce qui concerne MacOS, voici ce que j'obtiens dans le finder et sur mon bureau : macOS finder

macOS bureau

En ce qui concerne Windows 10, voici ce que j'obtiens :

windows 10 explorer

En ce qui concerne Windows 7, en dehors du fait que l'image n'est pas pivotée, les résultats sont identiques :

windows 7 explorer

Enfin sous Android, voici ce que j'obtiens (en vidéo) :

On constate donc que les systèmes d'exploitations ne génèrent pas les miniatures, mais utilisent celles qui sont présentes dans les métadonnées des fichiers.

D'un point de vue de l'investigation numérique, il est important de faire attention à ce genre de choses, sinon on risque de passer à coté.

En bonus, à partir des captures de l'éditeur hexadécimal, ceux qui le souhaitent pourront tenter de me dire quand j'ai pris ces photos (plutôt facile) mais surtout où je les ai prises (un peu plus compliqué)...

Machine learning avec OVH prescience en quelques clics

Machine learning avec OVH prescience en quelques clics

Dans ses group labs, OVH a lancé prescience, une plateforme de machine learning. C'est gratuit pendant la phase alpha et j'ai donc décidé de tester le future produit.

Il faut demander un accès en bas de la page dédié à prescience et quelque temps après, on reçoit un mail avec le token pour s'identifier sur la pateforme :

mail prescience

J'ai utilisé un des dataset de base pour le machine learning : iris. Il s'agit d'un tableau contenant 150 entrées (disponible dans son intégralité sur cet article wikipédia).

On commence par créer un fichier csv avec les 150 entrées :

sepal_lenght;sepal_width;petal_lenght;petal_width;species
5.1;3.5;1.4;0.2;setosa
4.9;3.0;1.4;0.2;setosa
4.7;3.2;1.3;0.2;setosa
4.6;3.1;1.5;0.2;setosa
[...]
7.0;3.2;4.7;1.4;versicolor
6.4;3.2;4.5;1.5;versicolor
6.9;3.1;4.9;1.5;versicolor
5.5;2.3;4.0;1.3;versicolor
6.5;2.8;4.6;1.5;versicolor
[...]
6.3;3.3;6.0;2.5;virginica
5.8;2.7;5.1;1.9;virginica
7.1;3.0;5.9;2.1;virginica
6.3;2.9;5.6;1.8;virginica
6.5;3.0;5.8;2.2;virginica
[...]

En peut ensuite uploader ce fichier dans prescience :

source upload

Après avoir cliqué sur upload, le système télécharge le fichier et commence à le parser :

source parsing

Lorsqu'il a terminé, on peut préprocesser la source :

source preprocess

Le système nous demande alors quelques informations, notamment le label et le type de problème. Dans notre cas, tout est sélectionné automatiquement :

source preprocess détails

Lorqu'on clique sur sur preprocess, le système lance le préprocessing en quatre étapes :

preprocess

Les deux premières étapes sont plutôt rapides. Les deux suivantes peuvent prendre un peu de temps. Lorsqu'il a terminé le préprocessing, on peut lancer l’optimisation :

dataset optimise

Le système nous demande des paramètres que nous laissons tels quels :

dataset optimise détails

Lorsqu'on clique sur optimise, on obtient quelque chose comme ça :

optimise

Il faut maintenant le laisser travailler un peu. En effet, cette phase peut prendre du temps. À l'issue, on peut lancer l'entrainement :

dataset train

Le système nous demande un nom de modèle :

dataset train détails

Lorsqu'on clique sur train, on obtient cela :

trainning

Quand l'entrainement est terminé, on peut interroger le système. Par exemple, si on entre une longueur des pétales de 1.5 cm, une largeur des sépales de 3 cm, une largeur des pétales de 0.25 cm et une largeur des sépales de 5 cm, le système nous indique qu'il s'agit, avec une probabilité de 1, de l'espèce setosa(ce qui est le cas) :

query

Si on clique sur explain, on obtient le joli graphique suivant :

shap form

Enfin, il est possible d'interroger le système avec curl :

$ curl -X POST "https://prescience-serving.ai.ovh.net/eval/iris/transform-model" -H "Authorization: $token" -H "Content-Type: application/json" -d '{"arguments":{"petal_lenght":1.5,"sepal_width":3,"petal_width":0.25,"sepal_lenght":5}}'
{"result":{"species":"setosa","probability(setosa)":1.0,"probability(versicolor)":0.0,"probability(virginica)":0.0},"arguments":{"imputed_petal_lenght":{"dataType":"double","value":1.5,"opType":"continuous"},"imputed_sepal_width":{"dataType":"double","value":3.0,"opType":"continuous"},"imputed_petal_width":{"dataType":"double","value":0.25,"opType":"continuous"},"imputed_sepal_lenght":{"dataType":"double","value":5.0,"opType":"continuous"},"scaled_imputed_petal_lenght":{"dataType":"double","value":-1.2266889625508433,"opType":"continuous"},"scaled_imputed_sepal_width":{"dataType":"double","value":-0.15118917210787064,"opType":"continuous"},"scaled_imputed_petal_width":{"dataType":"double","value":-1.1917094761129958,"opType":"continuous"},"scaled_imputed_sepal_lenght":{"dataType":"double","value":-1.0113190222659707,"opType":"continuous"}}}

C'est un exemple très simple mais cela fonctionne plutôt bien.

Ainsi, si on possède des données, on peut, sans rien connaitre ou comprendre à l'apprentissage automatisé, mettre en œuvre du machine learning en quelques clics.

Merci OVH.