From 6855667d3b0f6bb07ff1deb84f167abc92b5d915 Mon Sep 17 00:00:00 2001 From: Romain Avouac <43444134+avouacr@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:21:01 +0100 Subject: [PATCH] Corrections tp vectorisation + improve badge creation (#465) * make ssp cloud badges more personalisable * corrections * fix ssp cloud init --- content/NLP/04_word2vec.qmd | 142 ++++++++++++++++++------------------ sspcloud/init-jupyter.sh | 2 - utils.py | 21 ++---- 3 files changed, 80 insertions(+), 85 deletions(-) diff --git a/content/NLP/04_word2vec.qmd b/content/NLP/04_word2vec.qmd index 4fd49921d..becb062dd 100644 --- a/content/NLP/04_word2vec.qmd +++ b/content/NLP/04_word2vec.qmd @@ -38,7 +38,7 @@ sys.path.insert(1, '../../') #insert the utils module from utils import print_badges #print_badges(__file__) -print_badges("content/NLP/04_word2vec.qmd") +print_badges("content/NLP/04_word2vec.qmd", ssp_cloud_service="pytorch") ``` ::: @@ -63,58 +63,24 @@ Ce notebook est librement inspiré de : * https://www.kaggle.com/meiyizi/spooky-nlp-and-topic-modelling-tutorial/notebook +::: {.callout-warning title="Packages à installer"} +Comme dans la [partie précédente](#nlp), il faut télécharger des librairies +spécialiséees pour le NLP, ainsi que certaines de leurs dépendances. -::: {.cell .markdown} -```{=html} - ``` ::: - ```{python} from collections import Counter @@ -243,21 +209,6 @@ Notre échantillon initial n'est pas équilibré (*balanced*) : on retrouve plus certains auteurs que d'autres. Afin d'obtenir un modèle qui soit évalué au mieux, nous allons donc stratifier notre échantillon de manière à obtenir une répartition similaire d'auteurs dans nos ensembles d'entraînement et de test. -```{python} -X_train, X_test, y_train, y_test = train_test_split(spooky_df['text_clean'].values, - spooky_df['author_encoded'].values, - test_size=0.2, - random_state=33, - stratify = spooky_df['author_encoded'].values) -``` - -Par exemple, les textes d'EAP représentent 40 % des échantillons d'entraînement et de test : - -```{python} -print(100*y_train.tolist().count(0)/(len(y_train))) -print(100*y_test.tolist().count(0)/(len(y_test))) -``` - Aperçu du premier élément de `X_train` : ```{python} @@ -385,16 +336,11 @@ Comme nous nous intéressons plus à l'effet de la vectorisation qu'à la tâche nous allons utiliser un algorithme de classification simple (un SVM linéaire), avec des paramètres non fine-tunés (c'est-à-dire des paramètres pas nécessairement choisis pour être les meilleurs de tous). ```{python} -clf = LinearSVC(max_iter=10000, C=0.1) +clf = LinearSVC(max_iter=10000, C=0.1, dual="auto") ``` Ce modèle est connu pour être très performant sur les tâches de classification de texte, et nous fournira donc un bon modèle de référence (*baseline*). Cela nous permettra également de comparer de manière objective l'impact des méthodes de vectorisation sur la performance finale. - - - - - Pour les deux premières méthodes de vectorisation (basées sur des fréquences et fréquences relatives des mots), on va simplement normaliser les données d'entrée, ce qui va permettre au SVM de converger plus rapidement, ces modèles étant sensibles aux différences d'échelle dans les données. @@ -465,6 +411,33 @@ On commence par une approche __"bag-of-words"__, i.e. qui revient simplement à représenter chaque document par un vecteur qui compte le nombre d'apparitions de chaque mot du vocabulaire dans le document. +Illustrons d'abord le principe à l'aide d'un exemple simple. + +```{python} +corpus = [ + 'Un premier document à propos des chats.', + 'Un second document qui parle des chiens.' +] + +vectorizer = CountVectorizer() +vectorizer.fit(corpus) +``` + +L'objet `vectorizer` a été "entraîné" (*fit*) sur notre corpus d'exemple contenant deux documents. Il a notamment appris le vocabulaire complet du corpus, dont on peut afficher l'ordre. + +```{python} +vectorizer.get_feature_names_out() +``` + +L'objet `vectorizer` entraîné peut maintenant vectoriser le corpus initial, selon l'ordre du vocabulaire affiché ci-dessus. + +```{python} +X = vectorizer.transform(corpus) +print(X.toarray()) +``` + +Quel score `F1` obtient-on finalement avec cette méthode de vectorisation sur notre problème de classification d'auteurs ? + ```{python} cv_bow = fit_vectorizers(CountVectorizer) ``` @@ -476,19 +449,48 @@ qui permet de tenir compte des fréquences *relatives* des mots. Ainsi, pour un mot donné, on va multiplier la fréquence d'apparition du mot dans le document (calculé comme dans la méthode précédente) par un terme qui pénalise une fréquence élevée du mot dans le corpus. L'image ci-dessous, empruntée à Chris Albon, illustre cette mesure: -![](https://chrisalbon.com/images/machine_learning_flashcards/TF-IDF_print.png) -*Source: [Chris Albon](https://chrisalbon.com/code/machine_learning/preprocessing_text/tf-idf/)* +![](tfidf.png) + +*Source: [Towards Data Science](https://towardsdatascience.com/tf-term-frequency-idf-inverse-document-frequency-from-scratch-in-python-6c2b61b78558)* La vectorisation `TF-IDF` permet donc de limiter l'influence des *stop-words* et donc de donner plus de poids aux mots les plus salients d'un document. -On observe clairement que la performance de classification est bien supérieure, -ce qui montre la pertinence de cette technique. +Illustrons cela à nouveau avec notre corpus d'exemple de deux documents. +```{python} +corpus = [ + 'Un premier document à propos des chats.', + 'Un second document qui parle des chiens.' +] + +vectorizer = TfidfVectorizer() +vectorizer.fit(corpus) +``` + +Là encore, le vectoriseur a "appris" le vocabulaire du corpus. + +```{python} +vectorizer.get_feature_names_out() +``` + +Et peut être utilisé pour calculer les scores TF-IDF de chacun des termes des documents. + +```{python} +X = vectorizer.transform(corpus) +print(X.toarray()) +``` + +On remarque que "chats" et "chiens" possèdent les scores les plus élevés, ce sont bien les termes les plus distinctifs. A l'inverse, les termes qui reviennent dans les deux documents ("un", "document", "des") ont un score inférieur, car ils sont beaucoup présents dans le corpus relativement. + +Quel score `F1` obtient-on avec cette méthode de vectorisation sur notre problème de classification d'auteurs ? ```{python} cv_tfidf = fit_vectorizers(TfidfVectorizer) ``` +On observe clairement que la performance de classification est bien supérieure, +ce qui montre la pertinence de cette technique. + ## Word2vec avec averaging On va maintenant explorer les techniques de vectorisation basées sur les @@ -507,7 +509,7 @@ de la même manière que les composantes principales produites par une ACP. ![](w2v_vecto.png) -*Source: https://medium.com/@zafaralibagh6/simple-tutorial-on-word-embedding-and-word2vec-43d477624b6d* +*Source: [Medium](https://medium.com/@zafaralibagh6/simple-tutorial-on-word-embedding-and-word2vec-43d477624b6d)* __Pourquoi est-ce intéressant ?__ diff --git a/sspcloud/init-jupyter.sh b/sspcloud/init-jupyter.sh index 8a61a4b78..fea74ab54 100755 --- a/sspcloud/init-jupyter.sh +++ b/sspcloud/init-jupyter.sh @@ -25,6 +25,4 @@ cp "${COURSE_DIR}/${SECTION}/${CHAPTER}.ipynb" "${WORK_DIR}" rm -rf $CLONE_DIR # Open the relevant notebook when starting Jupyter Lab -jupyter server --generate-config -sudo chown -R ${USERNAME}:${GROUPNAME} ${HOME} echo "c.LabApp.default_url = '/lab/tree/${CHAPTER}.ipynb'" >> /home/onyxia/.jupyter/jupyter_server_config.py diff --git a/utils.py b/utils.py index a271abbec..313cb8b24 100644 --- a/utils.py +++ b/utils.py @@ -6,6 +6,7 @@ def reminder_badges( type = ['md','html'], split = None, onyxia_only = False, + ssp_cloud_service="python", GPU=False, correction = False ): @@ -73,13 +74,10 @@ def reminder_badges( if correction: onyxia_init_args.append("correction") - if GPU is True: - service_name = "jupyter-pytorch-gpu" - else: - service_name = "jupyter-python" + gpu_suffix = "-gpu" if GPU else "" - sspcloud_jupyter_link_launcher = f"https://datalab.sspcloud.fr/launcher/ide/{service_name}"\ - "?autoLaunch=true&onyxia.friendlyName=%C2%ABpython-datascience%C2%BB"\ + sspcloud_jupyter_link_launcher = f"https://datalab.sspcloud.fr/launcher/ide/jupyter-{ssp_cloud_service}{gpu_suffix}"\ + f"?autoLaunch=true&onyxia.friendlyName=%C2%AB{chapter_no_extension}%C2%BB"\ "&init.personalInit=%C2%ABhttps%3A%2F%2Fraw.githubusercontent.com%2Flinogaliana%2Fpython-datascientist%2Fmaster%2Fsspcloud%2Finit-jupyter.sh%C2%BB"\ f"&init.personalInitArgs=%C2%AB{'%20'.join(onyxia_init_args)}%C2%BB&security.allowlist.enabled=false" @@ -94,13 +92,8 @@ def reminder_badges( if split == 4: sspcloud_jupyter_link = f'{sspcloud_jupyter_link}
' - if GPU is True: - service_name = "vscode-pytorch-gpu" - else: - service_name = "vscode-python" - - sspcloud_vscode_link_launcher = f"https://datalab.sspcloud.fr/launcher/ide/{service_name}"\ - "?autoLaunch=true&onyxia.friendlyName=%C2%ABpython-datascience%C2%BB"\ + sspcloud_vscode_link_launcher = f"https://datalab.sspcloud.fr/launcher/ide/vscode-{ssp_cloud_service}{gpu_suffix}"\ + f"?autoLaunch=true&onyxia.friendlyName=%C2%AB{chapter_no_extension}%C2%BB"\ "&init.personalInit=%C2%ABhttps%3A%2F%2Fraw.githubusercontent.com%2Flinogaliana%2Fpython-datascientist%2Fmaster%2Fsspcloud%2Finit-vscode.sh%C2%BB"\ f"&init.personalInitArgs=%C2%AB{'%20'.join(onyxia_init_args)}%C2%BB&security.allowlist.enabled=false" @@ -174,6 +167,7 @@ def print_badges( onyxia_only=False, split=5, type="html", + ssp_cloud_service="python", GPU = False, correction=False): @@ -182,6 +176,7 @@ def print_badges( type=type, split=split, onyxia_only=onyxia_only, + ssp_cloud_service=ssp_cloud_service, GPU=GPU, correction=correction )