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:
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.
On mobilise les packages necessaires
library(slurm) # optionnel pour multicore
library(parallel)
library(readr)
library(syuzhet)# ncr
library(quanteda) #with quanteda
library("quanteda.dictionaries")
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
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))
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.
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])
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"))
))
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)
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")
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")
svtexte=split(df$text,fsplit[1:ntweets])
print(system.time(
resliwc <- mclapply(1:ncores, function(i) liwcalike(svtexte[[i]],dictionary = dict_liwc_french))
))
resliwc <- Reduce(rbind, resliwc)
df_nrcliwc <- cbind(df,resliwc)
write_rds(df_nrcliwc,"df_nrcliwc.rds")
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)
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])
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)
L’étape REDUCE regroupe les résultats du job slurm lancées sur le cluster auparavant
resnrc <- get_slurm_out(sjobnrc, outtype = 'table')
Est identique pour l’ordinateur ou pour le cluster
df_nrcliwc <- cbind(df,resnrc)
write_rds(df_nrcliwc,"df_nrcliwc.rds")
dict_liwc_french <- dictionary(file = "FrenchLIWCDictionary.dic",
format = "LIWC")
svtexte=split(df$text,fsplit[1:ntweets])
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)
resliwc <- get_slurm_out(sjobliwc, outtype = 'table')
df_nrcliwc <- cbind(df_nrcliwc,resliwc)
write_rds(df_nrcliwc,"df_nrcliwc.rds")
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
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