Travaux pratiques - Analyse de textes avec Spark NLP

(une variante de ce TP utilisant le langage Scala)

Références externes utiles :

L’objectif de cette séance de TP est de faire une rapide introduction à l’utilisation de Spark NLP. Spark NLP est une bibliothèque développée pour Spark par la société John Snow Labs NLP et permettant de réaliser un assez grand nombre d’opérations de traitement automatique pour plusieurs langues. Cette bibliothèque est aujourd’hui bien positionnée par rapport à ses concurrents, voir par exemple ce comparatif.

Pour préparer les répertoires et télécharger les données employées dans les exemples de cette séance, ouvrez une fenêtre terminal et entrez les commandes suivantes :

%%bash
mkdir -p tpnlp/data
wget -nc http://cedric.cnam.fr/vertigo/Cours/RCP216/docs/texteEn.txt -P tpnlp/data
wget -nc http://cedric.cnam.fr/vertigo/Cours/RCP216/docs/texteFr.txt -P tpnlp/data
# Lancer pyspark avec utilisation de SparkNLP :
pyspark --packages com.johnsnowlabs.nlp:spark-nlp_2.12:4.2.0

Sous Windows, créez le répertoire tpnlp et le sous-répertoire data, téléchargez ce premier fichier de données et ce second fichier de données dans le sous-répertoire data, ouvrez une fenêtre invite de commandes et lancez pyspark --packages com.johnsnowlabs.nlp:spark-nlp_2.12:4.2.0.

Note

Les solutions exposées nécessitent un accès Internet durant toute la session Spark qui emploie Spark NLP car différentes parties sont téléchargées lors de la session, suivant les besoins. Il est également possible de télécharger les ressources de Spark NLP en amont et d’exécuter la session Spark hors connexion, comme décrit dans cette documentation de John Snow LABS.

Traitement d’un fichier texte en anglais

D’abord, une vérification du répertoire de travail pour s’assurer que les chemins vers les fichiers de données sont corrects :

!pwd

Nous appliquerons d’abord certains traitements au fichier en anglais texteEn.txt (le contenu textuel d’une page Wikipedia). Dans Spark, il faut donc lire ce fichier dans un DataFrame.

from pyspark.sql.functions import lit
spark = SparkSession.builder.getOrCreate()

texteEnTout = spark.read.text("tpnlp/data/texteEn.txt").toDF("text")
texteEnTout.show(10)

Une fois ce travail effectué, nous allons éliminer les lignes de texte qui sont vides :

from pyspark.sql.functions import length
texteEn = texteEnTout.where(length(texteEnTout.text) > 0)
texteEn.show(10)

Le fichier texte a été lu dans un DataFrame qui possède une seule colonne appelée text (les traitements prédéfinis que nous regarderons dans la suite attendent une colonne ayant ce nom comme colonne d’entrée). Chaque paragraphe (terminé par un saut de ligne) est lu dans un item du DataFrame. Avec .where(length(texteEnTout.text) > 0) nous éliminons les items vides (correspondant aux paragraphes vides entre deux sauts de lignes successifs).

Question

À l’aide de Spark, déterminer combien de lignes non-vides comporte le fichier textEn.txt.

Correction

print(texteEn.count())

Spark NLP propose aussi bien des traitements séparés (les annotateurs) que des regroupements prédéfinis de plusieurs traitements (les pipelines). La documentation disponible pourrait sans doute être plus détaillée.

Commençons par importer les modules utiles :

from pyspark.ml import Pipeline
from sparknlp.pretrained import PretrainedPipeline
from sparknlp import Finisher

Nous appliquons d’abord des traitements prédéfinis dans un pipeline Spark NLP, explain_document_ml : découpage en phrases, découpage en tokens (mots), lemmatisation, étiquetage morpho-syntaxique. Les résultats sont ensuite traités par un annotateur (en terminologie Spark NLP) appelé finisher qui rend les résultats plus facilement lisibles par un humain. Pour appliquer le pipeline Spark NLP et ensuite le finisher aux résultats, nous intégrons les deux dans un pipeline Spark :

# Charge le modèle pré-entraîné "explain_document_ml")
explainPipelineModel = PretrainedPipeline("explain_document_ml").model
# Le Finisher permet d'annoter les résultats de façon lisible par un humain
finisherExplainEn = Finisher().setInputCols("token", "lemmas", "pos")
# Création d'un Pipeline Spark qui applique le modèle puis l'annotateur
pipelineExplainEn = Pipeline().setStages([explainPipelineModel,finisherExplainEn])

Le traitement est ensuite appliqué aux données et les résultats sont affichés, vous pouvez les examiner :

modelExplainEn = pipelineExplainEn.fit(texteEn)
annoteTexteEn = modelExplainEn.transform(texteEn).cache()
annoteTexteEn.printSchema()
annoteTexteEn.show()

Question

Quelles sont les différentes colonnes accessibles dans le DataFrame annoteTexteEn ? À quoi correspondent-elles ?

Afficher les 10 premières lignes pour :

  • la colonne correspondant à la tokenisation ;

  • la colonne correspondant à la lemmatisation ;

  • la colonne correspondent à l’étiquetage morpho-syntaxique (part-of-speech tagging ou POS).

Correction

annoteTexteEn.select("finished_token").show(truncate=False)
annoteTexteEn.select("finished_lemmas").show(truncate=False)
annoteTexteEn.select("finished_pos").show(truncate=False)

Expérimentation avec l’analyse des sentiments

Spark NLP propose également d’autres pipelines prédéfinis, voir la liste ou un outil de recherche, dont un nommé analyze_sentiment qui a pour objectif d’associer à chaque phrase une étiquette indiquant si les sentiments transmis par la phrase sont positifs ou négatifs. Nous faisons ci-dessous un essai de ce pipeline sur des données bien plus courtes, écrites directement dans l’interface de Spark et que vous pouvez modifier à loisir :

sentimentPipelineModel = PretrainedPipeline("analyze_sentiment").model
finisherSentiment = Finisher().setInputCols("document","sentiment")
pipelineSentiment = Pipeline().setStages([sentimentPipelineModel,finisherSentiment])

Nous pouvons l’appliquer sur un DataFrame contenant des données de test, pour expérimenter :

testSentimentData = spark.createDataFrame([ \
                      (1.0, "The movie is great."), \
                      (0.0, "But the cinema is quite dirty.") \
                ], ["label", "text"])
modelSentiment = pipelineSentiment.fit(testSentimentData)
sentimentTestSentimentData = modelSentiment.transform(testSentimentData)
sentimentTestSentimentData.show(truncate=False)

Question

À quoi correspondent les valeurs de la colonne finished_sentiment du DataFrame sentimentTestSentimentData ?

Modifier la phrase de test de sorte à n’avoir que des sentiments positifs.

Traitement d’un fichier texte en français

Enfin, examinons maintenant un autre pipeline, explain_document_md, appliquée au document en français texteFr.txt (l’équivalent plus court en français de la page Wikipedia en anglais considérée plus haut). Ce pipeline inclut également l’identification d’entités nommées (Named Entity Recognition, NER, colonne ner) et leur extraction (colonne entities).

Comme précédemment, commençons par charger le texte français dans un DataFrame en éliminant les lignes vides.

from pyspark.sql.functions import col,length

texteFr = spark.read.text("tpnlp/data/texteFr.txt").toDF("text").where(length(col("text")) > 0)
print(texteFr.count())

Spark NPL propose un modèle d’explication de documents pour la langue française, que nous pouvons charger de façon analogue au PretrainedPipeline utilisé plus haut. Attention, les ressources téléchargées sont volumineuses et cela peut prendre un certain temps.

explainPipelineModel = PretrainedPipeline("explain_document_md","fr").model
finisherExplainFr = Finisher().setInputCols("token", "lemma", "pos", "ner", "entities")
pipelineExplainFr = Pipeline().setStages([explainPipelineModel,finisherExplainFr])

modelExplainFr = pipelineExplainFr.fit(texteFr)
annoteTexteFr = modelExplainFr.transform(texteFr)

annoteTexteFr.printSchema()
annoteTexteFr.show(5, truncate=False)

Vous pouvez examiner le résultat des opérations de lemmatisation (qui ont pour objectif de remplacer chaque mot par sa forme « canonique ») et l’identification des entités nommées.

Question

Examinez les entités nommées de la première phrase du document (c’est-à-dire la première ligne du DataFrame). Quelles sont-elles ? De quels types sont-elles ?

# Afficher la première ligne
texteFr.show(1, truncate=False)
# À compléter
# ...
annoteTexteFr.select("finished_ner").show(1, truncate=False)
annoteTexteFr.select("finished_entities").show(1, truncate=False)

Pour terminer, notons que Spark NLP permet d’afficher la liste des ressources disponibles, et notamment des modèles d’analyse de texte qui sont disponibles. Par exemple, pour afficher tous les Pipelines disponibles pour l’analyse de texte en français (lang=fr) :

from sparknlp.pretrained import ResourceDownloader

ResourceDownloader.showPublicPipelines(lang="fr")

Nous nous servirons de Spark NLP dans une séance de TP suivante afin de produire des représentations vectorielles de textes à classer.