Ce script succède et vient en complément au script LdC2_annotation_V01_bc.Rmd qui associe des sentiments (annotations) aux tweets à partir de trois méthodes et/ou dictionnaires NCR, LIWC et LSD.

Les scripts proposés cherchent à accélérer les calculs d’annotation car ceux-là sont les plus lourds (ils durent plusierus heures) et constituent un goulot d’étranglement à ce stade des analyses.

Deux pistes sont exploitées:

Accélération de calculs d’annotation pour l’association de sentiments aux tweets

La méthode

Sont développées et actualisées des méthodes de calcul BigData présentées par moi en 2016 à une conférence à Venise (http://archives.marketing-trends-congress.com/2016/pages/PDF/CALCIU_MOULINS_SALERNO.pdf) . Elles reposent essentiellement sur la méthode que j’ai appelée GeneRal Approach for Parallel Processing Actions ou « GRAPPA » (un clin d’œil à la boisson qui nous a inspiré là-bas). Il s’agit de combiner l’approche MapReduce qui a démocratisé les calculs BigData avec les solutions de calcul parallel disponibles sur un ordinateur multicoeur et sur les clusters d’ordinateurs.

Elements communs aux deux approches (multicore et cluster)

Packages communs

On mobilise les packages necessaires

library(slurm) # optionnel pour multicore
library(parallel)
library(readr)
library(syuzhet)# ncr
library(quanteda) #with quanteda
library("quanteda.dictionaries")

READ & UPDATE: données

Après avoir chargé les tweets collectées, on rajoute quelques variables utiles

df <- readRDS("df.rds")
# df <- df[sample(round(nrow(df)/1000,0)),] # pour tester localement en miniature ..
df$day<-as.numeric(format(df$created_at, "%d")) # jour
df$month<-as.numeric(format(df$created_at, "%m")) # mois
df$hour<-as.numeric(format(df$created_at, "%H")) # heure
df$year<-2020 # heure
df$day2<-as.factor(df$day)
df$Jour <- 0

CHUNK SIZE:

determination de la taille des segments de tweets pour les splits en fonction de leur nombre et du nombre de noeuds disponibles.

ncores = 4 # ou: detectCores()-1 
ntweets <- nrow(df)
fsplit = rep(1:ncores, each=round(ntweets/ncores+.5,0))

Calcul parallèle sur un ordinateur (implicit or multicore parallelism)

En répartisant les données segmentées (splitées) et leur calcul sur plusieurs coeurs à la fois on arrive a réduire la durée des calculs 2 à 3 fois.

NRC

Analyse du sentiment simple (negatif, positif), puis celui des émotions (confiance,peur,surprise,tristesse,dégoût,colère,anticipation,joie) ##### SPLIT: des données On divise (split ) l’ensemble des tweets en morceaux (chunks) de taille égale afin de les affecter à chacun des cœurs du processeur de l’ordinateur pour les traiter en parallèle.

svtexte=split(as.character(df$text),fsplit[1:ntweets])
MAP: mappage parallèle de la fonction qui associe des catégories de sentiments NRC au textes

La fonction mclapply mappe (affecte) l’annotation des sentiments NRC aux segments (chunks) de tweets produisant comme résultat une liste de tableaux qui contiennent les frequences des catégories de sentiments trouvés dans chaque texte. Dans la liste il y a autant de tableaux qu’il y a de cœurs.
Comme c’est ici que se concentre les calculs lourds nous y avons ajouté une fonctionalité (system.time) qui affiche leur durée

print(system.time(
 resnrc <- mclapply(1:ncores, function(i) get_nrc_sentiment(svtexte[[i]], language="french"))
))
REDUCE: regroupe les resultats de calcul de chaque cœur

L’étape REDUCE regroupe les tableaux d’annotation, produites par chaque coeur et qui se trouvent dans une liste, en un seul tableau en les empilant un sur l’autre ligne par ligne.

resnrc <- Reduce(rbind, resnrc)
SAVE:

Augmente le fichier (tableau) de tweets avec les frequences des categories de sentiments issues du calcul MapReduce précedent, en rajoutant 10 colonnes.

df_nrcliwc <- cbind(df,resnrc)
write_rds(df_nrcliwc,"df_nrcliwc.rds")

LIWC

L’analyse de sentiments est complété par trois groupes de variables (de 80 variables) : celles liées aux proches ( ami, famille, humains), celles liée à la physiologie (alimentation, corps,sexualité,santé ) et enfin celle liée à la dimension saptiotemporelle

dict_liwc_french <- dictionary(file = "FrenchLIWCDictionary.dic",
                               format = "LIWC")
SPLIT:
svtexte=split(df$text,fsplit[1:ntweets]) 
MAP:
print(system.time(
 resliwc <- mclapply(1:ncores, function(i) liwcalike(svtexte[[i]],dictionary = dict_liwc_french))
))
REDUCE:
resliwc <- Reduce(rbind, resliwc)
SAVE:
df_nrcliwc <- cbind(df,resliwc)
write_rds(df_nrcliwc,"df_nrcliwc.rds")

Calcul parallèle sur un cluster (cluster parallelism)

En répartisant les calculs sur plusieurs ordinateurs (noeuds) sur un cluster on arrive a réduire substantiellement leur durée (jusqu’à des dizaines de fois).

Il s’agit du cluster HPC (High Performance Computing) de type grille de calcul du notre méso-centre de l’unversité. Les calculs sur le Cluster s’effectuent par l’intermédiaire d’un gestionnaire de travaux qui s’occupe de gérer la file d’attente et de lancer les calculs lorsque les ressources demandées sont disponibles.

Le gestionnaire de travaux du Cluster est SLURM (Simple Linux Utility for Resource Management).

On mobilise les packages necessaires. S’ajoute ici le package rslurm (voir partie commune)

NRC

SPLIT des données

Comme la lecture des donnée et leur morcèlement (split) sont rapides, Cette phase reste identique qu’il s’agisse d’un ordinateur ou d’un cluster.

svtexte=split(as.character(df$text),fsplit[1:ntweets])
MAP: mappage parallèle de la fonction d’affectation des sentiments NRC aux textes

La fonction “slurm_apply” est un genre d’enveloppe pour “mclapply” du package “parallel”, elle dispatche les mêmes opérations sur le cluster d’ordinateurs. L’option submit = FALSE, au lieu de soumettre le job au cluster, enregistre les données et les scripts dans un répertoire pour que job puisse être soumis manuellement par la commade shell “sbatch submit.sh” à partir de ce répertoir. Ainsi on peut préparer la procédure (job) localement sur un ordinateur personnel et la soumettre ultérieurment au cluster.

sjobnrc <- slurm_apply(function(i) get_nrc_sentiment(svtexte[[i]], language="french") ,
                       data.frame(i=seq_along(svtexte)),
                       add_objects = c("get_nrc_sentiment","svtexte"),
                       jobname = 'nrc_4',
                       nodes = 4,
                       cpus_per_node = 2, submit = TRUE)
REDUCE: regroupe les morceaux

L’étape REDUCE regroupe les résultats du job slurm lancées sur le cluster auparavant

resnrc <- get_slurm_out(sjobnrc, outtype = 'table')
SAVE:

Est identique pour l’ordinateur ou pour le cluster

df_nrcliwc <- cbind(df,resnrc)
write_rds(df_nrcliwc,"df_nrcliwc.rds")

LIWC

dict_liwc_french <- dictionary(file = "FrenchLIWCDictionary.dic",
                               format = "LIWC")
SPLIT:
svtexte=split(df$text,fsplit[1:ntweets]) 
MAP:
sjobliwc <- slurm_apply(function(i) liwcalike(svtexte[[i]], dictionary = dict_liwc_french) ,
                        data.frame(i=seq_along(svtexte)),
                        add_objects = c("liwcalike","svtexte", "dict_liwc_french"),
                        jobname = 'liwc_4',
                        nodes = 4,
                        cpus_per_node = 2, submit = TRUE)
REDUCE:
resliwc <- get_slurm_out(sjobliwc, outtype = 'table')
SAVE:
df_nrcliwc <- cbind(df_nrcliwc,resliwc)
write_rds(df_nrcliwc,"df_nrcliwc.rds")

Performances obtenue sur les cluster

Les opérations d’annotation des tweets 1401244 collectés après 20 jours de confinement qui prennent 5 ou 6h peuvent être réduites à 9mn ( sur 32 machines à 2 coeurs), dont 8 min pour l’annotation NRC et moins de 1 min sur LIWC.

26355677_0 nrc_32 1 2 4Gn 16? 00:08:13

26355677_0.+ batch 1 2 4Gn 1126392K 1126392K 00:08:13

26355677_1 nrc_32 1 2 4Gn 16? 00:08:28

26355677_1.+ batch 1 2 4Gn 1111044K 1111044K 00:08:28

26355677_2 nrc_32 1 2 4Gn 16? 00:07:51

26355677_2.+ batch 1 2 4Gn 1119704K 1119704K 00:07:51

26355677_3 nrc_32 1 2 4Gn 16? 00:09:50

26355677_3.+ batch 1 2 4Gn 1128304K 705460K 00:09:50

26355677_4 nrc_32 1 2 4Gn 16? 00:08:51

26355677_4.+ batch 1 2 4Gn 1119784K 1119784K 00:08:51

26355677_5 nrc_32 1 2 4Gn 16? 00:08:37

26355677_5.+ batch 1 2 4Gn 1127524K 1127524K 00:08:37

26355677_6 nrc_32 1 2 4Gn 16? 00:08:31

26355677_6.+ batch 1 2 4Gn 1131588K 715316K 00:08:31

26355677_7 nrc_32 1 2 4Gn 16? 00:08:38

26355677_7.+ batch 1 2 4Gn 1123872K 1123872K 00:08:38

26355677_8 nrc_32 1 2 4Gn 16? 00:08:36

26355677_8.+ batch 1 2 4Gn 1130848K 1130848K 00:08:36

26355677_9 nrc_32 1 2 4Gn 16? 00:08:21

26355677_9.+ batch 1 2 4Gn 1123564K 1123564K 00:08:21

26355677_10 nrc_32 1 2 4Gn 16? 00:08:41

26355677_10+ batch 1 2 4Gn 1122848K 1122848K 00:08:41

26355677_11 nrc_32 1 2 4Gn 16? 00:08:37

26355677_11+ batch 1 2 4Gn 1139140K 1139140K 00:08:37

26355677_12 nrc_32 1 2 4Gn 16? 00:08:30

26355677_12+ batch 1 2 4Gn 1127052K 270628K 00:08:30

26355677_13 nrc_32 1 2 4Gn 16? 00:08:34

26355677_13+ batch 1 2 4Gn 1134584K 704616K 00:08:34

26355677_14 nrc_32 1 2 4Gn 16? 00:09:38

26355677_14+ batch 1 2 4Gn 1150872K 1150872K 00:09:38

26355696_15 liwc_32 1 2 4Gn 16? 00:00:51

26355696_15+ batch 1 2 4Gn 1071108K 1071108K 00:00:51

26355696_0 liwc_32 1 2 4Gn 16? 00:00:43

26355696_0.+ batch 1 2 4Gn 1240020K 1240020K 00:00:43

26355696_1 liwc_32 1 2 4Gn 16? 00:00:51

26355696_1.+ batch 1 2 4Gn 1142816K 1142816K 00:00:51

26355696_2 liwc_32 1 2 4Gn 16? 00:01:05

26355696_2.+ batch 1 2 4Gn 1006808K 916824K 00:01:05

26355696_3 liwc_32 1 2 4Gn 16? 00:01:03

26355696_3.+ batch 1 2 4Gn 1100296K 340444K 00:01:03

26355696_4 liwc_32 1 2 4Gn 16? 00:00:47

26355696_4.+ batch 1 2 4Gn 1148692K 1148692K 00:00:47

26355696_5 liwc_32 1 2 4Gn 16? 00:00:50

26355696_5.+ batch 1 2 4Gn 1111580K 1111580K 00:00:50

26355696_6 liwc_32 1 2 4Gn 16? 00:00:53

26355696_6.+ batch 1 2 4Gn 1069416K 1069416K 00:00:53

26355696_7 liwc_32 1 2 4Gn 16? 00:00:53

26355696_7.+ batch 1 2 4Gn 1053316K 1053316K 00:00:53

26355696_8 liwc_32 1 2 4Gn 16? 00:00:57

26355696_8.+ batch 1 2 4Gn 1036104K 1036104K 00:00:57

26355696_9 liwc_32 1 2 4Gn 16? 00:00:53

26355696_9.+ batch 1 2 4Gn 1064304K 1064304K 00:00:53

26355696_10 liwc_32 1 2 4Gn 16? 00:00:51

26355696_10+ batch 1 2 4Gn 1076480K 1076480K 00:00:51

26355696_11 liwc_32 1 2 4Gn 16? 00:00:54

26355696_11+ batch 1 2 4Gn 1089424K 1089424K 00:00:54

26355696_12 liwc_32 1 2 4Gn 16? 00:00:53

26355696_12+ batch 1 2 4Gn 1099464K 1099464K 00:00:53

26355696_13 liwc_32 1 2 4Gn 16? 00:00:54

26355696_13+ batch 1 2 4Gn 1093252K 1093252K 00:00:54

26355696_14 liwc_32 1 2 4Gn 16? 00:00:51

26355696_14+ batch 1 2 4Gn 1093260K 1093260K 00:00:51

26355712 liwc_32 1 1 4Gn 836K 836K 00:00:00

Augmentation des perfomance avec le nombre de coeurs (1,2,4,8,16)

Sur une version antérieure du fichiers de tweets collectées plus de trois fois plus petite avec 401713 tweets les calcul des annotations NRC sur 1 noeud duraient 1h30. En doublant succéssivement le nombre de noueds on constate clairement la réduction du temps de calcul presque linaire avec l’augmentation du nombre de noeuds

Noeuds Temps

2 40

4 19

8 9

16 4