Licence CC BY

Deviner qui est l'auteur d'un message grâce au Machine Learning

On va essayer de savoir qui je suis

Je sais qui je suis, mais quelques uns m’ont déjà posé cette question : "Qui es-tu ?" et ben, c’est dans ce billet que je souhaite répondre.

Je me doute bien que si je me contente de dire, je suis XXX, on risque de me traiter de menteur, alors j’aimerai apporter la preuve que je suis qui je suis avec l’aide d’algorithmes de machine learning.

Sans plus attendre, raisonnons ensemble !

Allons à la recherche d'informations sur moi

Tout ce que l’on sait sur moi se trouve sur Zeste de Savoir. Et, croyez le ou pas, tout ce que l’on dit sur internet, même derrière un pseudonyme, permet de nous identifier. Sur internet on peu changer notre adresse email, notre pseudonyme, notre adresse ip, notre FAI, mais quelque chose de très compliqué à changer, c’est notre façon de parler/d’écrire. Et c’est comme ça que je vais vous prouver que je suis ce que je suis.

Nous allons nous concentrer sur tout ce que j’ai pu dire sur les forums de Zeste de Savoir. Pour cela, j’ai écrit un script qui me permet de collecter l’ensemble de mes messages sur le site.

Nous allons partir de l’hypothèse suivante : le jour ou j’ai écris cet article, j’étais infiltré dans le staff. Ce qui signifie donc que la liste des suspects est réduite aux membres du staff ce jour là.

C’est la raison pour laquelle on va aussi télécharger l’ensemble des messages des membres du staff à cette époque ainsi que des messages de ceux dont on est certain que ce n’est pas moi (des contres exemples quoi).

En résumé, voici les données que je vous propose de télécharger pour tester le code donné par la suite.

Lien de téléchargement

Le contenu de l’archive est le suivant :

fichier/dossier Description
staff.csv Fichier csv contenant la liste des membres du staff à l’époque ou le fameux article a été publié.
data.csv Liste des phrases que l’on trouve sur le forum. La colonne target est affectée à 1 lorsque c’est une phrase écrite par willard et à 0 si la phrase n’a pas été écrite par lui.
sentences Répertoire avec une liste de fichiers csv contenant des phrases de plusieurs membres, téléchargées sur le forum Zeste de Savoir
build_model.py Script de construction du modèle
guess.py Script qui permet de deviner qui est willard.

Une fois les fichiers téléchargés, vous devez obtenir l’arborescence suivante:

.
├── build_model.py
├── data.csv
├── guess.py
├── sentences
│   ├── sentences_10199.csv
│   ├── sentences_101.csv
│   ├── sentences_110.csv
│   ├── sentences_1309.csv
│   ├── sentences_138.csv
│   ├── sentences_141.csv
│   ├── sentences_143.csv
│   ├── sentences_155.csv
│   ├── sentences_2315.csv
│   ├── sentences_241.csv
│   ├── sentences_243.csv
│   ├── sentences_244.csv
│   ├── sentences_267.csv
│   ├── sentences_276.csv
│   ├── sentences_288.csv
│   ├── sentences_2.csv
│   ├── sentences_326.csv
│   ├── sentences_3349.csv
│   ├── sentences_379.csv
│   ├── sentences_542.csv
│   ├── sentences_5500.csv
│   ├── sentences_588.csv
│   ├── sentences_615.csv
│   ├── sentences_65.csv
│   ├── sentences_66.csv
│   ├── sentences_67.csv
│   ├── sentences_68.csv
│   ├── sentences_69.csv
│   ├── sentences_72.csv
│   ├── sentences_73.csv
│   ├── sentences_747.csv
│   ├── sentences_74.csv
│   ├── sentences_80.csv
│   ├── sentences_819.csv
│   ├── sentences_82.csv
│   ├── sentences_90.csv
│   ├── sentences_92.csv
│   └── sentences_94.csv
└── staff.csv

Construisons notre modèle de prédiction

Installation des libs externes

Pour nos travaux nous travaillons avec python 3.6. Nous aurons besoin d’installer les libs python suivantes:

pip install pandas
pip install scikit-learn

Séparer nos données (train et test)

Pour construire notre modèle, nous allons commencer par séparer nos données en deux catégories. Les données d’entrainement (celles qui vont servir à entraîner notre modèle) et les données de validation (celles qui vont permettre de valider que notre modèle est juste).

L’opération se fait grâce au code suivant :

import os
import pandas as pd
from sklearn.model_selection import train_test_split

# Import des données
data = pd.read_csv('data.csv')
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))

texts = data.sentence
target = data.target

# definir la colonne target comme étant de type booléen
target = target.astype('bool')

# split des données en deux catégories
sentences_train, sentences_test, y_train, y_test = train_test_split(texts.values, target.values, test_size=0.3, random_state=1000)

Nous venons d’effectuer une séparation dont 30% de nos données initiales serviront pour l’apprentissage.

La vectorisation du texte

Notre variable principale (la colonne X) contient surtout du texte. Le problème c’est que le texte ne se donne pas comme cela aux algorithmes, il faut au préalable le vectoriser. C’est à dire, transformer une phrase en une liste de nombre. On peut le voir comme la version mathématique de la phrase.

Il existe plusieurs types de vectorisation, et chacun s’utilise en fonction des cas que l’on veut traiter. Pour notre cas, nous utiliserons la vectorisation par comptage de mots.

En python ça donnera ceci :

from sklearn.feature_extraction.text import CountVectorizer

vectorizer = CountVectorizer()
vectorizer.fit(sentences_train)

# vectorise les phrases d'entrainement et de test avec CountVectorizer
X_train = vectorizer.transform(sentences_train)
X_test  = vectorizer.transform(sentences_test)

Maintenant que l’on dispose de la forme vectorisée des phrases, on enregistre le modèle de vectorisation dans un fichier (on le réutilisera quand il faudra effectuer les prédictions).

import pickle
pickle.dump(vectorizer, open(os.path.join(CURRENT_DIR, "vectorize.data"), 'wb'))

L’apprentissage du modèle

Nos phrases sont vectorisées, cela signifie que l’on peut les passer à un algorithme. Mais la question principale qui se pose est :

Quel est l’algorithme dois-je utiliser ?

Hey oui, il y en a tellement des algorithmes, mais on sait néanmoins choisir les algorithmes en fonction du type de nos variables. Par exemple observons ce que nous avons :

  • Nos variables sont des vecteurs (oui, on vient de les calculer)
  • On essaye de prédire une cible de type booléenne (willard or not willard ?)

Vous l’avez deviné, on est tout a fait dans le cas à traiter par une régression logistique.

  • Régression parce qu’on essaye de prédire quelque chose, on parle aussi d’expliquer un résultat en fonction d’une ou plusieurs variables.
  • Logistique parce que la variable que l’on essaye de prédire n’est pas quantitative, mais qualitative (booléenne dans notre cas).

C’est parti pour le code d’apprentissage.

from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
# l'apprentissage se fait bien sur la variable vectorisée de l'entrainement
model.fit(X_train, y_train)
# on calcule la précision du modèle sur les données de validation
score = model.score(X_test, y_test)

print("Precision:", score)

La précision renvoyée est :

Precision: 0.9800575263662512

On a donc ici une précision de 98% avec notre modèle d’apprentissage. Autant dire que c’est formidable. On saura, avec ce modèle prédire avec 98% de chances qu’une phrase a été écrite par moi. Fantastique non ?

Vite, vite ! Enregistrons notre modèle.

import pickle

pickle.dump(model, open(os.path.join(CURRENT_DIR,"model.data"), 'wb'))

Vous pouvez tout simplement lancer la commande python build_model.py qui résume ce que l’on a vu dans cette section.

Deux fichiers seront crées, model.data et vectorize.data.

A la recherche de l'infiltré

L’infiltré est un membre de l’ancien staff qui écrit de la même manière que moi. On va donc chercher dans la liste des messages des membres de l’ancien staff, lesquels correspondent à des phrases de Willard.

Le code qui permet de le calculer est le suivant :

# guess.py
import os
import csv
import pickle
import pandas as pd

CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
# on charge le modèle de vectorisation
vectorizer = pickle.load(open(os.path.join(CURRENT_DIR,"vectorize.data"), 'rb'))
# on charge le modèle d'apprentissage
model = pickle.load(open(os.path.join(CURRENT_DIR,"model.data"), 'rb'))
res = []

def get_ids_from_file(filepath):
    """
    Cette fonction renvoi la liste des id dans un fichier à deux colonnes "id;username"
    """
    ids = []
    with open(filepath, 'r') as csvfile:
        readercsv = csv.reader(csvfile, delimiter=',', quotechar='"')
        next(readercsv)
        for row in readercsv:
            ids.append(row[0])

    return ids

def get_username_from_id(filepath, id):
    """
    Cette fonction renvoi le nom d'utilisateur correspondant à un id
    """
    with open(filepath, 'r') as csvfile:
        readercsv = csv.reader(csvfile, delimiter=',', quotechar='"')
        next(readercsv)
        for row in readercsv:
            if row[0] == id:
              return row[1]

    return None

def predict_from_sent(x):
    """
    Cette fonction renvoi True si la phrase est de willard.
    """
    p_value = model.predict(vectorizer.transform([x]))

    return p_value[0]

# on recupère la liste des ids des staff de l'époque
ids = get_ids_from_file("staff.csv")
for id in ids:
    data = pd.read_csv('sentences/sentences_{}.csv'.format(id), header=None)
    data["is_willard"] = data[1].map(predict_from_sent)
    guilty = data.loc[data['is_willard'] == True]
    res.append({"id": get_username_from_id("staff.csv", id), "guilty": len(guilty.index)})

print(sorted(res, key = lambda i: i['guilty'], reverse=True))

Vous pouvez tout simplement lancer la commande python guess.py.

Si vous lancer le code chez vous (après avoir construit le modèle), vous pouvez retrouver qui sur ce forum, s’amuse à parler comme moi.

Pour les plus paresseux (mais je n’en dirais pas plus), sachez que le coupable se trouve parmi les cinq membres suivants (il s’agit des cinq membres dont les messages ressemblent furieusement à ma façon d’écrire):

  • Andr0
  • Arius
  • firm1
  • Kje
  • ShigeruM

Bon courage.


L’objectif de ce billet était de montrer que, plus on poste sur les forums, les réseaux sociaux, plus les algorithmes arriveront à nous identifier.

Maintenant que vous savez qui je suis, une question éthique se pose

Est-ce que je peux être sanctionné uniquement parce qu’un algorithme l’a décidé ? Souhaitons nous faire confiance aux machines pour prendre des décisions importantes ?

Je vous laissez réfléchir !

37 commentaires

Ça te fait quel âge maintenant ?

Ge0

Avec tous ses voyages spatio-temporels, il ne doit pas passer énormément de temps sur la Terre. Pour peu qu’il ait été au voisinage d’un trou noir ou d’un astre massif du même gabarit, il ne doit pas avoir beaucoup vieilli ! 14 ans me semble une bonne estimation.

+2 -0

Pour les plus paresseux (mais je n’en dirais pas plus), sachez que le coupable se trouve parmi les cinq membres suivants (il s’agit des cinq membres dont les messages ressemblent furieusement à ma façon d’écrire):

Et on sait que parmi cette liste, on peut écarter ceux qui ne savent pas coder en Python. :D

L’étau se resserre.

+3 -0

QUOI ? Un Multi-compte pour un membre du Staff ? Quelle honte ! Scandale ! Remboursé !


Nickel cet article. Même si je connais déjà la réponse, j’admire la façon de l’amener !

De la même façon, en recoupant le temps entre le premier message/inscription d’un membre et le contenu de ce premier message on pourrait anticiper les comptes de spam ?

+11 -0

De la même façon, en recoupant le temps entre le premier messagae/inscription d’un membre et le contenu de ce premier message on pourrait anticiper les comptes de spam ?

Oui ! J’ai commencé à jouer un peu sur la bêta à détecter les comptes de spam. En prenant tous les comptes qui ne se sont pas reconnectés après s’être inscrit (intervalle de moins de 5 ou 10 minutes entre la date d’inscription et de dernière connexion) et qui ont une biographie non vide qui contient les mots clés "UK" ou "Essay" out "Writing" j’ai pu trouver des comptes qui avaient échappés à notre vigilance. Effectivement avec un algorithme d’apprentissage machine ça peut être encore plus efficace. Il suffit de l’entraîner sur les comptes bannis avec une longue biographie (principalement des comptes de spam) puis tester sur tous les comptes de la base de données.

Édition En alternative on peut aussi prendre les noms de comptes de spam, qui sont souvent de la forme "prénom anglais" + "nom anglais" + 2 chiffres, le tout en minuscule. Mais bon, ça fait moins de données à analyser qu’une biographie donc je pense que la détection serait plus faible.

On pourrait aussi analyser leur adresse de courriel qui a aussi un pattern reconnaissable. Mais là encore ça ne fait que quelques caractères et chiffres.

+3 -0

Il suffit de l’entraîner sur les comptes bannis avec une longue biographie (principalement des comptes de spam) puis tester sur tous les comptes de la base de données.

L’idéal serait surtout de créer des faux comptes sur la bêta, d’apprendre à l’algo à reconnaître les patterns désiré (url suspecte dans la bio ou le champ site web, multiples posts ayant exactement le même contenu/contenu similaire, post publicitaire unique similaire à un post ayant déjà fait l’objet d’un acte de modération, etc.) et voir s’il détecte bien les comptes créés car il ne faut pas oublier que tous les comptes spam n’ont pas forcément des mots clés dans leur bio, même si cela est le cas de ceux qui nous embêtent dernièrement. L’algo devrait être capable de détecter une gamme de patterns (tout en ayant conscience des faux positifs éventuels car ce n’est pas à exclure).

Ensuite, si l’idée d’avoir un tel algo en production intéresse, simplement faire en sorte qu’il signale les comptes suspects et laisser le staff gérer.

+1 -0

Moi j’ai essayé vite fait de les détecter avec des mots clés, mais si j’avais poussé mes investigations j’aurais fait un petit algorithme d’apprentissage machine, ce qui se rapproche plus de la détection de pattern que de mots clés (si on a suffisamment de vrais comptes spam sous la main).

Tu proposes de créer de faux comptes spam, mais si on fait ça on aura un algorithme entraîné à reconnaître nos faux comptes de spam. Rien ne nous dit qu’il sera bon à reconnaître les vrais comptes de spam ! D’ailleurs, je ne suis pas sûr d’être capable de créer moi-même un bon faux compte de spam. :)

+0 -0

Moi j’ai essayé vite fait de les détecter avec des mots clés, mais si j’avais poussé mes investigations j’aurais fait un petit algorithme d’apprentissage machine, ce qui se rapproche plus de la détection de pattern que de mots clés (si on a suffisamment de vrais comptes spam sous la main).

Tu proposes de créer de faux comptes spam, mais si on fait ça on aura un algorithme entraîné à reconnaître nos faux comptes de spam. Rien ne nous dit qu’il sera bon à reconnaître les vrais comptes de spam !

Situphen

L’idée est surtout de l’entraîner sur les deux. Il faut bien tester la détection et AMHA, pour cela, il faut 1) l’apprendre à reconnaître un certain pattern sur des comptes déjà banni et des faux-comptes mais ensuite 2) tester cet apprentissage sur des comptes spam créés au préalable et voir d’une manière heuristique si l’algo retourne un/des résultats.

Il faut bien mettre la détection à l’épreuve (car se limiter aux comptes que nous avons déjà banni (et donc dont on connait le pattern) risque de faire en sorte que l’algo soit trop restrictif ou incapable de s’adapter à des comptes qui ne correspondent pas tout-à-fait au pattern appris) ;)

D’ailleurs, je ne suis pas sûr d’être capable de créer moi-même un bon faux compte de spam. :)

T’en fais pas. Avec la bande de filous que nous avons au sein du staff et de la communauté, ça, c’est le dernier de nos soucis. :D

+0 -0

Ensuite, si l’idée d’avoir un tel algo en production intéresse, simplement faire en sorte qu’il signale les comptes suspects et laisser le staff gérer.

Ah oui hein, surtout pas d’autoban, laissons la présomption d’innocence… Mais une petite alerte dans le panel du staff par contre…

+6 -0

Ensuite, si l’idée d’avoir un tel algo en production intéresse, simplement faire en sorte qu’il signale les comptes suspects et laisser le staff gérer.

Ah oui hein, surtout pas d’autoban, laissons la présomption d’innocence… Mais une petite alerte dans le panel du staff par contre…

Eskimon

Yep.

Avec une option "Ce compte n’est pas suspect" permettant d’affiner la détection de l’algo au fil du temps.

+1 -0

À propos de l’étude qui est mené dans ce billet.
Il y a des sources à ce sujet ? Des recherches universitaires ? Un papier peut-être ?

+1 -0

En fait, c’est un compte partagé entre tous ces gens là. D’où le résultat.

SpaceFox

Vu le retard pris pour les Zaward, s’ils s’y sont mis à 5, ça en deviens honteux. :ninja:

+9 -0

Pour les plus paresseux (mais je n’en dirais pas plus), sachez que le coupable se trouve parmi les cinq membres suivants (il s’agit des cinq membres dont les messages ressemblent furieusement à ma façon d’écrire):

Et on sait que parmi cette liste, on peut écarter ceux qui ne savent pas coder en Python. :D

L’étau se resserre.

Renault

On peut aussi écarter ceux qui ne se sont pas connectés depuis un an. ;)

+0 -0

Pour les plus paresseux (mais je n’en dirais pas plus), sachez que le coupable se trouve parmi les cinq membres suivants (il s’agit des cinq membres dont les messages ressemblent furieusement à ma façon d’écrire):

Et on sait que parmi cette liste, on peut écarter ceux qui ne savent pas coder en Python. :D

L’étau se resserre.

Renault

On peut aussi écarter ceux qui ne se sont pas connectés depuis un an. ;)

nohar

J’ai essayé de comparer la liste de suspects et leurs dernières connexions (plusieurs fois…) mais la piste est coriace !

+0 -0

Je n’ai pas le temps de faire une étude plus poussée mais en analysant les données EXIF (avec ce site web) des graphiques de Willard sur le sujet de zAwards, j’ai découvert qu’il utilise une certaine version de matplotlib. Quelqu’un peut investiguer sur les contenus des suspects pour voir quels logiciels ils utilisent ? Je sais que Inkscape laisse une trace, c’est probablement le cas pour d’autres outils. Cette enquête peut peut-être nous permettre de restreindre la liste des suspects !

+2 -0

Dans tout ça on oublie qu’on raisonne sur deux points qui sont litigieux de base :

  1. Willard prétends avoir été infiltré dans le staff, mais on a aucune autre preuve de ce qu’il avance lui-même.
  2. Le jeu de données a été fourni par Willard lui-même, sans les méthodes exactes qui ont permit de le générer et donc qui permettraient de le régénérer.

Donc, Willard nous demande de croire des conclusions sur la base d’informations fournies par Willard et en l’état invérifiables. Donc, prudence.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte