.. _artificieltokenizefeaturesrst: ========================= Des mots aux sacs de mots ========================= .. only:: html **Links:** :download:`notebook `, :downloadlink:`html `, :download:`PDF `, :download:`python `, :downloadlink:`slides `, :githublink:`GitHub|_doc/notebooks/lectures/artificiel_tokenize_features.ipynb|*` La tokenisation consiste à découper un texte en *token*, l’approche *sac de mots* consiste à compter les occurences de chaque mot dans chaque document de la base de données. .. code:: ipython3 from jyquickhelper import add_notebook_menu add_notebook_menu() .. contents:: :local: .. code:: ipython3 texte = """ Mardi 20 février, à la médiathèque des Mureaux (Yvelines), le chef de l’Etat a accompagné la locataire de la rue de Valois pour la remise officielle du rapport sur les bibliothèques, rédigé par leur ami commun, l’académicien Erik Orsenna, avec le concours de Noël Corbin, inspecteur général des affaires culturelles. L’occasion de présenter les premières mesures en faveur d’un « plan bibliothèques ». """ Pipeline de traitement ---------------------- Maintenant qu’on sait découper en mots ou couples de mots, il faut appliquer sur une liste de textes. On crée une petite liste de textes. .. code:: ipython3 import pandas df = pandas.DataFrame(dict(text=[texte, "tout petit texte"])) df .. raw:: html
text
0 \nMardi 20 février, à la médiathèque des Murea...
1 tout petit texte
Et on applique l’objet `CountVectorizer `__ : .. code:: ipython3 from sklearn.feature_extraction.text import CountVectorizer cd = CountVectorizer() cd.fit(df["text"]) res = cd.transform(df["text"]) res .. parsed-literal:: <2x51 sparse matrix of type '' with 51 stored elements in Compressed Sparse Row format> On récupère une `matrice sparse `__ où chaque colonne compte le nombre d’occurence d’un mot dans le texte : .. code:: ipython3 res.todense() .. parsed-literal:: matrix([[1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 5, 2, 1, 1, 1, 1, 1, 1, 1, 1, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1], [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, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0]], dtype=int64) Les mots sont les suivants : .. code:: ipython3 cd.vocabulary_ .. parsed-literal:: {'mardi': 27, '20': 0, 'février': 19, 'la': 22, 'médiathèque': 30, 'des': 13, 'mureaux': 29, 'yvelines': 50, 'le': 23, 'chef': 7, 'de': 12, 'etat': 17, 'accompagné': 2, 'locataire': 26, 'rue': 43, 'valois': 49, 'pour': 38, 'remise': 42, 'officielle': 33, 'du': 14, 'rapport': 41, 'sur': 45, 'les': 24, 'bibliothèques': 6, 'rédigé': 44, 'par': 35, 'leur': 25, 'ami': 4, 'commun': 8, 'académicien': 1, 'erik': 16, 'orsenna': 34, 'avec': 5, 'concours': 9, 'noël': 31, 'corbin': 10, 'inspecteur': 21, 'général': 20, 'affaires': 3, 'culturelles': 11, 'occasion': 32, 'présenter': 40, 'premières': 39, 'mesures': 28, 'en': 15, 'faveur': 18, 'un': 48, 'plan': 37, 'tout': 47, 'petit': 36, 'texte': 46} Un tokenizer différent ---------------------- La classe `CountVectorizer `__ est facilement paramétrable. On peut en particulier changer le *tokenizer* : .. code:: ipython3 from nltk.tokenize import word_tokenize .. code:: ipython3 count_vect = CountVectorizer(tokenizer=word_tokenize) counts = count_vect.fit_transform(df["text"]) counts.shape .. parsed-literal:: (2, 62) .. code:: ipython3 counts.todense() .. parsed-literal:: matrix([[1, 1, 6, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 5, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 4], [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, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0]], dtype=int64) Hashing ------- Le nombre de mots distincts peut être très grand surtout si la source des textes est bruitée (faute d’orthographe, spams, …). Pour réduire le nombre de mots, on peut utiliser un *hash* à valeur dans un ensemble plus petit que le nombre de mots découverts : c’est une sorte de modulo. Deux mots pourront être comptabilisés dans la même colonne. On utilise la classe `HashingVectorizer `__. .. code:: ipython3 from sklearn.feature_extraction.text import HashingVectorizer cd = HashingVectorizer(n_features=5) cd.fit(df["text"]) res = cd.transform(df["text"]) res.todense() .. parsed-literal:: matrix([[-0.08247861, 0.16495722, -0.41239305, 0.74230749, 0.49487166], [-0.4472136 , 0. , 0.89442719, 0. , 0. ]]) La classe utilise la classe `FeatureHasher `__ et plus précisément le code dans `\_hashing.pyx `__. .. code:: ipython3 cd = HashingVectorizer(n_features=5, binary=True) cd.fit(df["text"]) res = cd.transform(df["text"]) res.todense() .. parsed-literal:: matrix([[0.4472136 , 0.4472136 , 0.4472136 , 0.4472136 , 0.4472136 ], [0.70710678, 0. , 0.70710678, 0. , 0. ]]) Réduire les dimensions tout en gardant une certaine forme de proximité. .. code:: ipython3 df2 = pandas.DataFrame(dict(text=[texte, " ".join(texte.split()[1:-1]), " ".join(texte.split()[5:-5]), " ".join(texte.split()[10:-10]) + ' machine', " ".join(texte.split()[20:-20]) + ' learning', " ".join(texte.split()[25:-25]) + ' statistique', " ".join(texte.split()[30:-30]) + ' nouveau', "tout petit texte"])) df2 .. raw:: html
text
0 \nMardi 20 février, à la médiathèque des Murea...
1 20 février, à la médiathèque des Mureaux (Yvel...
2 médiathèque des Mureaux (Yvelines), le chef de...
3 chef de l’Etat a accompagné la locataire de la...
4 de Valois pour la remise officielle du rapport...
5 officielle du rapport sur les bibliothèques, r...
6 bibliothèques, rédigé par nouveau
7 tout petit texte
.. code:: ipython3 cd = HashingVectorizer(n_features=8, binary=False) cd.fit(df2["text"]) res = cd.transform(df2["text"]) res.todense() .. parsed-literal:: matrix([[-0.08873565, 0.3549426 , -0.1774713 , 0. , -0.26620695, 0. , 0.79862086, 0.3549426 ], [-0.09053575, 0.36214298, -0.18107149, 0. , -0.18107149, 0. , 0.81482171, 0.36214298], [-0.10783277, 0.43133109, -0.21566555, 0. , 0. , 0. , 0.75482941, 0.43133109], [-0.24806947, 0.3721042 , 0. , -0.12403473, -0.12403473, 0. , 0.86824314, 0.12403473], [-0.20412415, 0.81649658, 0. , 0.20412415, 0.20412415, 0.20412415, 0.40824829, 0. ], [ 0. , 0.35355339, 0.35355339, 0.35355339, 0. , 0.70710678, -0.35355339, 0. ], [ 0. , 0. , 0. , 0. , 0. , 0. , 0.70710678, -0.70710678], [ 0. , 0. , 0. , 0. , 0. , 0. , 1. , 0. ]]) .. code:: ipython3 from sklearn.metrics.pairwise import pairwise_distances pandas.DataFrame(pairwise_distances(res)) .. raw:: html
0 1 2 3 4 5 6 7
0 0.000000 0.087352 0.293731 0.388511 0.916931 1.561800 1.171556 0.634632
1 0.087352 0.000000 0.217844 0.368633 0.883337 1.564650 1.166111 0.608569
2 0.293731 0.217844 0.000000 0.455795 0.797058 1.543129 1.241976 0.700244
3 0.388511 0.368633 0.455795 0.000000 0.826704 1.561579 0.973412 0.513336
4 0.916931 0.883337 0.797058 0.826704 0.000000 1.130625 1.192749 1.087889
5 1.561800 1.564650 1.543129 1.561579 1.130625 0.000000 1.581139 1.645329
6 1.171556 1.166111 1.241976 0.973412 1.192749 1.581139 0.000000 0.765367
7 0.634632 0.608569 0.700244 0.513336 1.087889 1.645329 0.765367 0.000000
tf-idf ------ Ce genre de technique produit des matrices de très grande dimension qu’il faut réduire. On peut enlever les mots rares ou les mots très fréquents. `td-idf `__ est une technique qui vient des moteurs de recherche. Elle construit le même type de matrice (même dimension) mais associe à chaque couple (document - mot) un poids qui dépend de la fréquence d’un mot globalement et du nombre de documents contenant ce mot. .. math:: idf(t) = \log \frac{\# D}{\#\{d \; | \; t \in d \}} Où : - :math:`\#D` est le nombre de documents - :math:`\#\{d \; | \; t \in d \}` est le nombre de documents contenant le mot :math:`t` :math:`f(t,d)` est le nombre d’occurences d’un mot :math:`t` dans un document :math:`d`. .. math:: tf(t,d) = \frac{1}{2} + \frac{1}{2} \frac{f(t,d)}{\max_{t' \in d} f(t',d)} On construit le nombre :math:`tfidf(t,f) = tf(t,d) idf(t)` : Le terme :math:`idf(t)` favorise les mots présent dans peu de documents, le terme :math:`tf(t,f)` favorise les termes répétés un grand nombre de fois dans le même document. On applique à la matrice précédente. Sur deux documents, cela ne sert pas à grand-chose. On utilise la classe `TfidfVectorizer `__. .. code:: ipython3 from sklearn.feature_extraction.text import TfidfVectorizer cd = TfidfVectorizer() cd.fit(df["text"]) res = cd.transform(df["text"]) res.todense() .. parsed-literal:: matrix([[0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.20100756, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.50251891, 0.20100756, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.40201513, 0.20100756, 0.20100756, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0. , 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0. , 0. , 0.10050378, 0.10050378, 0.10050378], [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.57735027, 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.57735027, 0.57735027, 0. , 0. , 0. ]]) Si on décompose : .. code:: ipython3 from sklearn.feature_extraction.text import TfidfTransformer from sklearn.pipeline import make_pipeline pipe = make_pipeline(CountVectorizer(), TfidfTransformer()) pipe.fit(df['text']) res = pipe.transform(df['text']) res.todense() .. parsed-literal:: matrix([[0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.20100756, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.50251891, 0.20100756, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.40201513, 0.20100756, 0.20100756, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0. , 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0.10050378, 0. , 0. , 0.10050378, 0.10050378, 0.10050378], [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.57735027, 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.57735027, 0.57735027, 0. , 0. , 0. ]]) Ca marche aussi sur les hash. .. code:: ipython3 pipe = make_pipeline(HashingVectorizer(n_features=5), TfidfTransformer()) pipe.fit(df['text']) res = pipe.transform(df['text']) res.todense() .. parsed-literal:: matrix([[-0.06142775, 0.17266912, -0.30713875, 0.77701104, 0.51800736], [-0.4472136 , 0. , 0.89442719, 0. , 0. ]]) Sur un jeu de données --------------------- L’idée est d’appliquer une LDA ou `Latent Dirichet Application `__. .. code:: ipython3 from papierstat.datasets import load_tweet_dataset tweet = load_tweet_dataset() tweet = tweet[tweet["text"].notnull()] tweet.head(n=2).T .. raw:: html
0 1
index 776066992054861825 776067660979245056
nb_user_mentions 0 0
nb_extended_entities 0 0
nb_hashtags 1 1
geo NaN NaN
text_hashtags , SiJétaisPrésident , SiJétaisPrésident
annee 2016 2016
delimit_mention NaN NaN
lang fr fr
id_str 7.76067e+17 7.76068e+17
text_mention NaN NaN
retweet_count 4 5
favorite_count 3 8
type_extended_entities [] []
text #SiJétaisPrésident se serait la fin du monde..... #SiJétaisPrésident je donnerai plus de vacance...
nb_user_photos 0 0
nb_urls 0 0
nb_symbols 0 0
created_at Wed Sep 14 14:36:04 +0000 2016 Wed Sep 14 14:38:43 +0000 2016
delimit_hash , 0, 18 , 0, 18
.. code:: ipython3 pipeline = make_pipeline(CountVectorizer(), TfidfTransformer()) res = pipeline.fit_transform(tweet['text']) .. code:: ipython3 count = pipeline.steps[0][-1] voc = count.get_feature_names() Le code suivant marche parce que la base n’est pas trop petite. .. code:: ipython3 data = pandas.DataFrame(res.todense(), columns=voc) data['whole_tweet'] = tweet['text'] data[data['œuvre'] > 0].head() .. raw:: html
00 000 0000 0079 00h 04 06 09 0ccnpoxuwu 0cxdedblpx ... îles îls œil œufs œuvre œuvrer œuvrerais œuvres δlex whole_tweet
109 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.354877 0.0 0.0 0.0 0.0 #Macron appelle à poursuivre l'œuvre de #Rocar...
151 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.328786 0.0 0.0 0.0 0.0 Quelle est-t-elle cette "œuvre de Michel Rocar...
4063 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.399841 0.0 0.0 0.0 0.0 #SiJetaisPresident je durcirais les pénalités ...

3 rows × 11925 columns

.. code:: ipython3 from sklearn.decomposition import LatentDirichletAllocation lda = LatentDirichletAllocation(n_components=10) lda.fit(res) .. parsed-literal:: c:\python365_x64\lib\site-packages\sklearn\decomposition\online_lda.py:536: DeprecationWarning: The default value for 'learning_method' will be changed from 'online' to 'batch' in the release 0.20. This warning was introduced in 0.18. DeprecationWarning) .. parsed-literal:: LatentDirichletAllocation(batch_size=128, doc_topic_prior=None, evaluate_every=-1, learning_decay=0.7, learning_method=None, learning_offset=10.0, max_doc_update_iter=100, max_iter=10, mean_change_tol=0.001, n_components=10, n_jobs=1, n_topics=None, perp_tol=0.1, random_state=None, topic_word_prior=None, total_samples=1000000.0, verbose=0) .. code:: ipython3 def print_top_words(model, feature_names, n_top_words): for topic_idx, topic in enumerate(model.components_): print("Topic #%d:" % topic_idx) print(" ".join([feature_names[i] for i in topic.argsort()[:-n_top_words - 1:-1]])) print() .. code:: ipython3 print_top_words(lda, voc, 10) .. parsed-literal:: Topic #0: sieste imposerai cantine frites scolaire self affaires obligatoire profiterais grec Topic #1: pizza domicile meufs purge constitution haute drogues trahison demissionerai do Topic #2: organiserai semaines mets organiserais kebabs lep roux raie cache empire Topic #3: abolirai ss10 desigual urgent intérieur avancés wiko morsay téléréalité lcp Topic #4: interdirais les sijetaispresident seraient port ballerines camembert démissionnerais droit gratuits Topic #5: rendrais écoles légal mandat poudlard obligatoire sortie vive bouton 8h Topic #6: légaliserai promesses famille illégal rendrai tiendrais arfa dutreil beuh sports Topic #7: sijetaispresident je ministre légaliserais co https culture nommerais les la Topic #8: sijetaispresident de je la les le et co https macron Topic #9: chocolatine bac pain kfc lenorman cc lait tacos notaires christineboutin .. code:: ipython3 comp = lda.transform(res) .. code:: ipython3 comp.shape .. parsed-literal:: (5087, 10) Pour chaque tweet, le score plus élevé indique la classe dans laquelle classé le tweet. .. code:: ipython3 val = comp[1,:] val, val.argmax(), tweet['text'][1] .. parsed-literal:: (array([0.02214216, 0.02214216, 0.02214216, 0.02214216, 0.02214224, 0.02214216, 0.02214216, 0.02214232, 0.80072034, 0.02214216]), 8, '#SiJétaisPrésident je donnerai plus de vacances 😌, genre 1mois de cours 1 mois de vacances etc...\r\nVotez pour moi !😂') On regarde les tweets les plus représentatifs du topic 3. .. code:: ipython3 prediction = pandas.DataFrame(comp) prediction['tweet'] = tweet['text'] t3 = prediction.sort_values(3, ascending=False) t3.head() .. raw:: html
0 1 2 3 4 5 6 7 8 9 tweet
383 0.021209 0.021209 0.021209 0.606043 0.021210 0.021209 0.021209 0.021210 0.224283 0.021209 Sondage @OdoxaSondages Décryptage demain en pl...
2391 0.026966 0.026966 0.026966 0.600969 0.026966 0.026966 0.026966 0.026966 0.183305 0.026966 Jss sure c'est Raouf qui a cree ce # 😂😂 @justd...
2354 0.039798 0.039799 0.039798 0.599248 0.039889 0.039798 0.039798 0.039870 0.082205 0.039798 #SiJetaisPresident j'abrogerais #college2016...
370 0.022561 0.022561 0.022561 0.591920 0.022561 0.022561 0.022561 0.022562 0.227592 0.022561 Sondage @13h15 / @OdoxaSondages. Décryptage de...
4706 0.020940 0.020940 0.020940 0.588835 0.020940 0.020940 0.020940 0.020941 0.243645 0.020940 Politiciens criminels #Sarkozy #Macron #LePen ...
.. code:: ipython3 print("\n---\n".join(list(t3['tweet'].head()))) .. parsed-literal:: Sondage @OdoxaSondages Décryptage demain en plateau dans le @13h15 de @LaurentDelahous #Macron https://t.co/wJex9S0Ute nice #Nice06 --- Jss sure c'est Raouf qui a cree ce # 😂😂 @justdelgrosso #SiJetaisPresident --- #SiJetaisPresident j'abrogerais #college2016... --- Sondage @13h15 / @OdoxaSondages. Décryptage demain en plateau dans le @13h15 de @LaurentDelahous #Précision #Macron https://t.co/wAEbrJxY7m --- Politiciens criminels #Sarkozy #Macron #LePen #Fabius (y a quand même des gosses morts pour ce dernier) la liste est longue #stopcorruption