Le Machine Learning (2/2) - exemples pratiques

machine learningpython
Paul Denoyes - 30/11/2018 à 17:14:370 commentaire

Pour faire suite à l’article de Hind, illustrons tout ça avec quelques exemples.


Exemple 1 : Détection de la langue (FR ou pas FR) avec un Naïve Bayes multinomial


L'objet de ce paragraphe est de mettre en place un modèle qui permettra de séparer les textes en langue française, des textes dans d'autres langues.


Nous sommes dans un cas d’apprentissage :

- Supervisé. Des phrases, séquences de mots, associées à des catégories – ici la langue de la séquence catégorisée en FR vs. pas FR.

- Probabiliste.

- Offline. Cependant, on pourrait l'utiliser de façon incrémentale au besoin, avec scikit-learn - méthode partial_fit.

- Paramétrique. Car nous utiliserons une distribution multinomiale, définie par des paramètres, et des probabilités conditionnelles, elles-mêmes des paramètres.

- Non linéaire. Dans le cas (le notre) d’une distribution multinomiale (cependant, les attributs coef_ et intercept_ permettent de passer au logarithme pour le rendre linéaire au besoin : Le naïve Bayes multinomial devient linéaire lorsqu'on l'exprime dans l'espace des logarithmes – ln(A x B) = ln(A) + ln(B)).


  • Théorie


Théorème de Bayes :

Notation : Probabilité P d'avoir A sachant B se note P(A|B).

Théorème : Pour A et B indépendants : P(A|B) = P(B|A) * P(A) / P(B)


Dans notre cas :

A : « piocher » une séquence de mots « cette courte phrase ».

B : se trouver dans la catégorie "Français".

B|A : se trouver dans la catégorie "Français" | sachant que l’on a « pioché » "cette courte phrase"

A|B : « piocher » une séquence de mots « cette courte phrase » lorsque l'on prend un élément de la catégorie "Français"


Comment classer dans la bonne catégorie une séquence que l’on n’avait pas dans notre jeu d’entrainement, sachant que dans ce cas P(A) n’est pas évaluable, P(B|A) non plus ?


  • On va considérer (même si c'est faux) qu'il n'y a pas de dépendance entre les mots, du coup on a le droit de dire que :


Probabilité de tirer la séquence "cette courte phrase" = P("cette courte phrase") = P("cette") x P("courte") x P("Phrase")

et

P("cette courte phrase"|"Français") = P("cette"|"Français") x P("courte"|"Français") x P("phrase"|"Français")


  • Maintenant quid de l’estimation pour une séquence dont un ou plusieurs mots de celle-ci ne seraient pas dans le corpus d’apprentissage ?


Si on ne connaît pas un des mots de la phrase, alors P(mot qu’on ne connait pas) = 0 et ça va mettre à 0 les formules ci-dessus.


Il existe alors plusieurs façons de s'affranchir de ce problème et d'estimer cette probabilité à autre chose que zéro, sans pour autant donner une valeur délirante.

Par exemple on pourrait utiliser la correction de Laplace (cf. additive smoothing) avec des variable discrètes, ou utiliser une distribution pour estimer la probabilité d'une variable continue (Imaginez que vous deviez, à l’aveugle donner une estimation de taille d’une personne que vous n'avez jamais vue. Si la taille suit une distribution Gaussienne, et disons que la moyenne est 1m65, hommes/femmes confondus. Sans autre information sur cette personne, vous pourriez approcher de la vérité en disant qu’elle mesure sans doute autours de 1m65.)


  • Un peu de code


import numpy as np
import nltk
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
import csv
from nltk.stem.porter import PorterStemmer
import pickle

reader = csv.reader(open('LanguageClassificationCorpusShuffled-V01.csv'), delimiter=";")
x = list(reader)
result = np.array(x)
#Chargement des données dans un format exploitable (liste ici, np.array souvent pour les dimensions)
X = result[:,0]
Y = result[:,1]
#Jeu d'entrainement
Xtrain = X[2000:-1]
Ytrain = Y[2000:-1]
#Jeu de tests
Xtest = X[0:1999]
Ytest = Y[0:1999]

####### TOKENIZATION

def tokenize(text):
   tokens = nltk.word_tokenize(text)
   stems = stem_tokens(tokens, stemmer)
   return stems

stemmer = PorterStemmer()
def stem_tokens(tokens, stemmer):
   stemmed = []
   for item in tokens:
       stemmed.append(stemmer.stem(item))
   return stemmed

######## 
   
############################################################
##                 APPRENTISSAGE                          ##
############################################################

from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline

text_clf = Pipeline([('vect', CountVectorizer(tokenizer=tokenize)),
                    ('tfidf', TfidfTransformer(norm='l2', use_idf=True, smooth_idf=True, sublinear_tf=False)),
                    ('clf', MultinomialNB(alpha=1.0)),
])

#Entrainement du modèle
text_clf = text_clf.fit(Xtrain, Ytrain)

############################################################
##               SAUVEAGARDE MODELE                       ##
############################################################

with open('modeleMNB-Langue-V01.pkl', 'wb') as f:
   pickle.dump(text_clf, f)

############################################################
##               MESURE PERFORMANCE                       ##
############################################################

predicted = text_clf.predict(Xtest)
prediction = np.mean(predicted == Ytest)
print("")
print("################# MNB MNB MNB #################")
print("Précision globale sur le jeu de test en mode MNB")
print("")
print (prediction)
print("")
print("Rapport de classification")
print("")
from sklearn import metrics
print(metrics.classification_report(Ytest, predicted))


> SORTIE


#################  MNB MNB MNB  #################
Précision globale sur le jeu de test en mode MNB

0.9309654827413707

Rapport de classification

             precision    recall  f1-score   support

          1       0.96      0.94      0.95      1446
          2       0.85      0.91      0.88       553

avg / total       0.93      0.93      0.93      1999


  • Interprétation du résultat


Les images ci-dessous définissent et illustrent la précision et le rappel.




- Précision : VP / (VP + FP)

- Rappel : VP / (VP + FN)




Maintenant qu'est-ce que le f1-score et à quoi sert-il ?


Définition : F1-score = 2 x (Précision x Rappel) / (Précision + Rappel)


À quoi ça sert : Permet d'évaluer la qualité du modèle, même lorsque celui-ci classe sur des données très déséquilibrées (beaucoup plus de données dans une classe que dans l'autre).


Imaginez que l'on veuille entraîner un modèle à diagnostiquer la narcolepsie, en fonction de mesures prises par un électroencéphalogramme.

Sachant que ce trouble ne touche que 0,05 % de la population, si je pose comme simple modèle :


def diagnose(data):
  return 'not narcoleptic'

print(diagnose([['whatever'], ['I', 'pass', 'here']]))


Qui retourne systématiquement le diagnostic : 'not narcoleptic'.


Pour 10.000 personnes


La précision sur la classe "non narcoleptique" sera de 99,95 % - ([9995 / (9995 + 5)] = 0.9995) - impressionnant.


Le F1-Score sera en revanche plus réaliste quant à la qualité de ce modèle : 0 %.

(tout comme le recall qui suffirait dans ce cas extrême, mais on a rarement un "mauvais" modèle aussi manichéen, en conditions réelles le F1-score montrera davantage son utilité)


Dans notre cas, nous avions des données déséquilibrées (beaucoup plus d’éléments en français que dans d'autres langues), nous pouvons constater en regardant un seul indice, le F1-score, que le modèle classe correctement.


Exemple 2 : "Distance" entre les mots (word embeddings - vectorization des mots)


Ce paragraphe décrira un exemple capable de déterminer la proximité sémantique des mots du corpus, afin par exemple de déterminer de quel sujet traite tel ou tel document.


Nous sommes dans un cas d’apprentissage :

- Non supervisé. On fournit simplement un texte et point.

- Géométrique. On va calculer des vecteurs, des distances entre des points de l’espace etc…

- Incrémental.

- Non paramétrique. L’algorithme place des points les uns par rapport aux autres.

- Non linéaire.


  • Le principe


Donner une représentation microscopique de chaque mot du corpus au travers d’une suite de chiffres, qui puisse macroscopiquement dessiner une représentation cohérente de l’ensemble du corpus.


Ces valeurs seront calculées sur la base du contexte de chaque mot (i.e. les mots qui l’entourent). “You get to know a word by the company it keeps.” — Firth, J. R. (1957)


Généralement :

. Soit par un modèle minimisant une fonction coût qui évalue la proximité de deux mots (chacun étant une suite de chiffres) à la probabilité qu’ils ont d'apparaître dans le même contexte (GloVe)

. Soit par un modèle prédictif (réseau de neurones qui va apprendre à prédire un mot cible en sortie en fonction des mots de contexte passés en entrée – Word2Vec)

C’est ce dernier modèle que nous allons illustrer.


  • Illustration


Corpus de texte : Le vif zéphyr jubile sur les kumquats du clown gracieux


Encodons le texte en considérant que chaque mot du corpus (cible) a pour contexte un seul mot à droite et un seul mot à gauche.



Continuous bag of words (CBOW) : On passe ensuite le contexte (i.e. 2 mots dans notre cas) dans un réseau de neurones avec une couche cachée de dimension N (le nombre de dimensions que l’on souhaite pour définir chaque mot).

On optimise afin que l’entrée (contexte) corresponde à la sortie (mot cible).



Skip-gram : idem mais dans l’autre sens, de la cible, prédire le contexte




  • Un peu de code


import unicodedata, re
import gensim

############################################################
##                APPRENTISSAGE                           ##
############################################################
try:
    model = gensim.models.Word2Vec.load("Word2Vec-UnsupervizedSimilarities.model")
except IOError:
    def remove_accents(text):
        nkfd_form = unicodedata.normalize('NFKD', text)
        return u"".join([c for c in nkfd_form if not unicodedata.combining(c)])

    def tokenize(text):
        return re.sub("\s+", " ", re.sub("[^A-Za-z]", " ", remove_accents(text))).split()

    stoplist = set('www com mais dans a le la les \n un une des me ma mes .  l l  l d  d d  h  h h  j cela de ou notre et pour dans qui tres très sont avons nous que de je est au du en à vous ce se sur votre avec tout bien il elle sur il ne par pas on'.split())
    FRENCH_STOP_WORDS = frozenset(['com', 'www', 'ou', '\n', 'notre', 'se', 'cela', 'la', 'me', 'à', 'vous', 'votre', 'j', 'j ', ' j', 'pas', 'ma', 'elle', 'en', 'par', 'est', 'avec', 'et', 'il', 'que', 'l', 'l ', ' l', '.', 'avons', 'un', 'tout', 'mais', 'sur', 'les', 'une', 'on', 'du', 'bien', 'nous', 'h', ' h', 'h ', 'ce', 'des', 'je', 'au', 'mes', 'sont', 'de', 'très', 'a', 'le', 'dans', 'd', ' d', 'd ', 'qui', 'tres', 'pour', 'ne'])

    def removeStopWords(text):
        return str([word for word in text.lower().split() if word not in stoplist])
    #On charge notre corpus 0 nettoyage + tokenization
    NomFichierApprentissage = "Datas-90K"
    ad_data = [removeStopWords(line.strip()) for line in open(NomFichierApprentissage + ".csv", encoding="latin-1")]
    text_data = [tokenize(ad_datum.lower()) for ad_datum in ad_data]
    model = gensim.models.Word2Vec(text_data, size=2000, window = 10, min_count = 5, workers=12)
model.save("Word2Vec-UnsupervizedSimilarities.model")

############################################################
##                  RESULTATS                             ##
############################################################

print("")
print("")
print("")
print("                  ON RESTE SUR UN VOCABLE SPECIALISEE HOTELERIE, POUR RESTER RACCORD AVEC LE CORPUS")

#TOUVER L'INTRUS
print("")
ListeIntrus = ["sejour", "vacances", "villegiature", "conge", "parking"]
print("")
print("")
print("## Trouver l'intrus dans la liste suivante %s" %ListeIntrus)
print("##")
print("##                >> " + model.wv.doesnt_match(ListeIntrus))
print("")

#SIMILARITE ENTRE DEUX MOTS (Entre 0 et 1)
MotsSimilaires = (("bon", "excellent"))
print("")
print("## Quelle similarité entre "'"%s"'", et "'"%s"'"" %(MotsSimilaires[0], MotsSimilaires[1]))
print("##")
print("##                >> " + str(model.wv.similarity(MotsSimilaires[0], MotsSimilaires[1])))
print("##")
print("")
MotsSimilaires = (("bon", "mauvais"))
print("")
print("## Quelle similarité entre "'"%s"'", et "'"%s"'"" %(MotsSimilaires[0], MotsSimilaires[1]))
print("##")
print("##                >> " + str(model.wv.similarity(MotsSimilaires[0], MotsSimilaires[1])))
print("##")
print("")

#DEDUCTIONS
print("")
positive=['chambre', 'terrasse']
negative = ['lit']
print("## LOGIQUE - ANALOGIES")
print("##")
print("## %s est à %s ce que %s est à " %(positive[0], negative[0], positive[1]) + "'" + str(model.wv.most_similar(positive=positive, negative = negative, topn=1)[0][0]) + "' " + "avec un indice de confiance de " + str(model.wv.most_similar(positive=positive, negative = negative, topn=1)[0][1]))
print("##")
positive=['chambre', 'sdb']
negative = ['lit']
print("## %s est à %s ce que %s est à " %(positive[0], negative[0], positive[1]) + "'" + str(model.wv.most_similar(positive=positive, negative = negative, topn=1)[0][0]) + "' " + "avec un indice de confiance de " + str(model.wv.most_similar(positive=positive, negative = negative, topn=1)[0][1]))
print("")

#TROUVER DES SYNONYMES
NombreFeatures = 5
MotSimilaire = ['repas']
similaires = model.wv.most_similar(positive=MotSimilaire, topn=NombreFeatures)
dissemblables = model.wv.most_similar(negative=MotSimilaire, topn=NombreFeatures)
#Graphique avec Matplotlib
import matplotlib.pyplot as plt
import numpy as np
features = []
coefs = []
for i in range (0, NombreFeatures):
    features = np.append(features, similaires[i][0])
    coefs = np.append(coefs, similaires[i][1])
for i in range (0, NombreFeatures):
    features = np.append(features, dissemblables[i][0])
    coefs = np.append(coefs, dissemblables[i][1])
featuresTuple = tuple(features)
coefsList = list(coefs)
y_similarite = np.arange(len(featuresTuple))
plt.figure(figsize=(10, 5))
colors = ['black' if c < 0.6 else 'lightblue' for c in coefsList]
plt.bar(y_similarite, coefs, align='center', alpha=0.9, color = colors)
plt.xticks(y_similarite, features, rotation=60, fontsize= 14)
plt.ylabel('Degré de similarité', fontsize= 20)
plt.title('Plus et moins similaires avec %s' %MotSimilaire, fontsize= 24)
plt.show()


  • Résultats


         ON RESTE SUR UN VOCABLE SPÉCIALISÉ HÔTELLERIE, POUR RESTER RACCORD AVEC LE CORPUS


## Trouver l'intrus dans la liste suivante ['sejour', 'vacances', 'villegiature', 'conge', 'parking']
##
##               >> parking


## Quelle similarité entre "bon", et "excellent"
##
##               >> 0.8612218442783093
##


## Quelle similarité entre "bon", et "mauvais"
##
##               >> 0.4286132022531659
##


## LOGIQUE - ANALOGIES
##
## chambre est à lit ce que terrasse est à 'exterieure' avec un indice de confiance de 0.566259503364563
##
## chambre est à lit ce que sdb est à 'sanitaires' avec un indice de confiance de 0.606020450592041

Graphique illustrant ce que le modèle est capable de trouver, par exemple les 5 mots les plus similaires pour lui au mot repas (en bleu), et les plus éloignés (en noir).

Commentaires :

Aucun commentaires pour le moment


Laissez un commentaire :

Réalisé par
Expaceo