poster

Voici le quatrième billet d'une série portant sur l'analyse du contenu textuel de commentaires Facebook en lien avec des articles de la presse écrite. Le troisième billet peut être lu ici.

Introduction

Alors que le troisième billet traitait de la modélisation et de la présentation des données qui sont analysées, nous traiterons ici de la méthodologie de traitement des données des articles et des commentaires, ainsi que de l'analyse statistique des commentaires.

Méthodologie et algorithmes

La plupart des analyses ont été effectuées à l'aide des différents algorithmes inclus dans la librairie NLTK et le logiciel Stanford CoreNLP [9].

Traitement des articles et des commentaires

La segmentation en phrases a été effectuée à l'aide de l'algorithme PunktSentenceTokenizer et la segmentation en mots à l'aide de ToktokTokenizer ou TweetTokenizer.

L'étiquetage des parties du discours et l'extraction des entités nommées dans les articles ont été effectués à l'aide de l'analyseur Stanford CoreNLP. Seules les étiquettes de parties du discours suivantes ont été conservées : ['ADJ',’ADV’,’INTJ’,’NOUN’,’PROPN’,’VERB’] dans un dictionnaire Python. Les entités nommées ont aussi été enregistrées dans un dictionnaire Python.

Les programmes suivants ont servi au traitement:

  • pretraitement.py: fonctions d'analyse communes
  • traitement_articles.py: traitement des articles
  • traitement_commentaires.py: traitement des commentaires

Traitement additionnel pour les commentaires

Les références aux auteurs ont été identifiées listant les auteurs des commentaires , pour chacun des articles, puis en identifiant ceux-ci dans le texte des autres commentaires.

Puis, les émojis ont été convertis en texte à l'aide de la librairie emoji pour Python [14]. Ils ont aussi été extraits dans un dictionnaire Python.

La lemmatisation en français a été effectuée à l'aide du French LEFFF Lemmatizer de Claude Coulombe [5], qui est compatible avec la syntaxe utilisée dans la librairie NLTK et les étiquettes de parties du discours utilisées dans WordNet.

On identifie les synsets réalistes pour chaque mot admissible depuis WordNet en convertissant les étiquettes de parties du discours identifiées depuis avec CoreNLP POS Tagger en étiquettes compatibles. On recherche ensuite le mot lemmatisé dans WordNet, et on filtre les résultats obtenus selon les parties du discours possibles.

Analyse statistique des commentaires

Les différentes métriques ont été calculées et les graphiques créés dans le programme analyse_articles.py qui sera présenté en détail dans un prochain article.

Distribution du niveau de langage

Nombre de jetons avec parties du discours dans WordNet, par commentaire, par média

On observe une médiane entre 2 et 5 pour le nombre de mots ayant une étiquette parties du discours et un synset dans Wordnet. Ce nombre est remarquablement plus élevé pour les commentaires sur les publications de RC. Dans presque tous les cas, il est possible d'utiliser au moins un synset pour inférer le sens de la publication et ainsi établir une relation sémantique avec l'article.

Proportion de jetons avec parties du discours dans WordNet, par commentaire, par média

Dans plus de la moitié des cas, 75 % des mots ayant une étiquette parties du discours dans les commentaires sont présents dans WordNet.

Nombre de types de parties du discours avec classes fermées, par commentaire, par média

La majorité des commentaires contiennent entre deux et quatre types d'étiquettes de parties du discours.

Distribution des marqueurs d'emphase

Émojis les plus fréquents, par médias

Les émojis les plus fréquents, par une large proportion, expriment le rire. Le plus fréquent est Face With Tears of Joy.

Nombre de mots en majuscules, par commentaire, par média

On retrouve généralement peu de mots en majuscules, mais les rares commentaires qui en contiennent en ont beaucoup. Cette caractéristique ne ferait cependant pas un bon attribut pour un modèle de classification.

Nombre de ponctuations successives totales, par commentaire, par média

On remarque qu'il y a une quantité non négligeable de commentaires qui contiennent des ponctuations successives. Ce serait un attribut à considérer dans un modèle de classification.

Indicateurs d'intertextualité

Le principal indicateur de l'intertextualité est la référence directe à l'auteur d'un commentaire précédent.

Proportion de références d'auteurs, par médias
Media Proportion
0 FIG 0.356813
1 RC 0.351153
2 TVA 0.0.351805

On remarque ici que la proportion de commentaires qui contiennent de telles références est constante et est aussi relativement élevée, peu importe le média. On pourrait donc conclure qu'il s'agit d'un attribut possédant de bonnes caractéristiques pour un modèle de classification binaire.

Un autre indicateur de l'intertextualité est l'usage de la deuxième personne, autant pour les pronoms que pour les articles possessifs. Cependant, ici, il n'est pas possible de savoir, sans devoir faire une analyse des coréférences, si la personne référée est l'auteur d'un commentaire ou une personne mentionnée dans l'article.

Proportion de commentaires avec des pronoms ou articles à la 2e personne, par médias
Media Proportion
0 FIG 0.135150
1 RC 0.200405
2 TVA 0.179176

Cet indicateur possède aussi de bonnes caractéristiques pour être un attribut dans un modèle de classification.

Analyse statistique des relations entre commentaires et articles

Entités nommées en commun

Une façon d'évaluer la pertinence d'un commentaire est de dénombrer le nombre d'entités nommées communes entre ce commentaire et l'article auquel il réfère. Ici, on calcule, pour chaque commentaire, le nombre d'entités en commun avec l'article, puis on effectue la moyenne par article. Cette statistique est représentée sur la figure qui suit.

Nombre moyen d'entités nommées en commun entre le commentaire et l'article, par article et par média

On remarque que le nombre moyen d'entités référées par commentaire est d'environ 1/5. On peut donc dire qu'en moyenne, 20 % des commentaires réfèrent à une entité nommée de l'article. Afin d'augmenter potentiellement ce nombre, on pourrait étendre la liste des entités nommées à l'aide de relations sémantiques de méronymie.

Groupes sémantiques en commun

Une autre façon d'évaluer la pertinence est d'accumuler tous les synsets possibles d'un article et de chacun de ses commentaires et de calculer l'intersection de ces ensembles. On calcule ensuite la proportion des synsets provenant du commentaire qui se trouvent dans l'intersection. Enfin, on calcule la moyenne, par article, de cette proportion. On a donc un indicateur moyen de la distance sémantique entre l'article et l'ensemble de ses commentaires.

Note : on calcule ici une proportion étant donné le nombre variable de synsets par parties du discours.

Proportion moyenne de synsets en commun entre le commentaire et l'article, par article et par média

On remarque ici une grande différence entre les distributions de cette statistique parmi les médias. Une hypothèse à vérifier serait que celle-ci permettrait de dresser un portrait global de la relation sémantique des commentaires produits par les abonnés d'un média aux articles publiés par celui-ci.

Conclusion

Dans ce rapport, on aborde les caractéristiques propres aux commentaires sur les réseaux sociaux. On décrit plusieurs approches possibles pour créer des attributs pour un modèle de classification de la pertinence. Des liens sont établis avec la plupart des techniques traditionnelles d'analyse et traitement du langage naturel. De plus, on présente certaines caractéristiques propres aux commentaires. Enfin, un exemple d'application est étudié et illustré avec plusieurs statistiques sur les attributs potentiels dans la modélisation. Les entités nommées et la sémantique pourraient représenter des composantes importantes pour mesurer la pertinence des commentaires.

En savoir plus sur le code

Dans les prochains billets, nous irons plus en détail dans le code Python qui a servi à la réalisation de ce projet.

Billet(s) précédent(s)

Références

Références additionnelles

Programmes

  • pretraitement.py
## pretraitement.py

import emoji
from nltk.tokenize import sent_tokenize

# Prétraitement
def pretraitement(article,tok,ner_tagger,pos_tagger):
    # tokeniser par phrases
    article_sentences = sent_tokenize(article)
    article_ner_tokens = []
    article_pos_tokens = []
    article_emoji_tokens = []
    for sentence in article_sentences:
        try:
            if len(sentence) > 0:
                # Tokeniser
                sentence_tokens = tok.tokenize(sentence)
                sentence_tokens = [emoji.demojize(token) for token in sentence_tokens if len(token)>0]
                if len(sentence_tokens) > 0:
                    emoji_tokens = [(token,i) for i, token in enumerate(sentence_tokens,1) if token[0] == ":"]
                    sentence_tokens = [token for token in sentence_tokens if token[0] != ":"]
                if len(sentence_tokens) > 0:
                    # Assembler les entités nommées et colocations
                    sentence_ner = ner_tagger.tag(sentence_tokens)
                    ner_tokens = [ner_token for ner_token in sentence_ner if ner_token[1] != 'O']
                    # Supprimer les classes fermées avec un POS
                    sentence_pos = pos_tagger.tag(sentence_tokens)
                    pos_tokens = [pos_token for pos_token in sentence_pos if pos_token[1] in ['ADJ','ADV','INTJ','NOUN','PROPN','VERB']]
                    # Ajouter à la liste de phrases tokenisées
                    article_ner_tokens.append(ner_tokens)
                    article_pos_tokens.append(pos_tokens)
                    article_emoji_tokens.append(emoji_tokens)
        except:
            pass
    return article_ner_tokens, article_pos_tokens, article_emoji_tokens

def aggreger_ner_tags(article):
    dict_named_entity = {}
    for sentence in article[0]:
        for entity in sentence:
            dict_named_entity[entity] = dict_named_entity.get(entity,0) + 1
    return dict_named_entity

def aggreger_pos_tags(article):
    dict_pos = {}
    for sentence in article[1]:
        for pos in sentence:
            dict_pos[pos] = dict_pos.get(pos,0) + 1
    return dict_pos

def aggreger_emoji(article):
    dict_emojis = {}
    for sentence in article[2]:
        for emoji,loc in sentence:
                dict_emojis.setdefault(emoji, []).append(loc)
    return dict_emojis
  • traitement_articles.py
import pandas as pd
import numpy as np
from nltk.corpus import stopwords
from nltk.tokenize import toktok, sent_tokenize
from nltk.parse import CoreNLPParser
import re
import pickle
import emoji
import pretraitement as pr
import os

tok = toktok.ToktokTokenizer()

pos_tagger = CoreNLPParser(url='http://localhost:9000', tagtype='pos')

ner_tagger = CoreNLPParser(url='http://localhost:9000', tagtype='ner')

textes_articles_df = pd.read_csv("refined_data/textes_articles_df.csv")
textes_articles_df = textes_articles_df[textes_articles_df["text"].notnull() & (textes_articles_df["media"]!='CNN')]

del textes_articles_df['Unnamed: 0']

article_pretraite = [pr.pretraitement(x,tok,ner_tagger,pos_tagger) for x in list(textes_articles_df["text"])]

textes_articles_df['ner_dict']=[pr.aggreger_ner_tags(article) for article in article_pretraite]
textes_articles_df['pos_dict']=[pr.aggreger_pos_tags(article) for article in article_pretraite]

os.mkdir("pickle")

f = open("pickle/textes_articles_df.pickle","wb")
pickle.dump(textes_articles_df,f)
f.close()
  • traitement_commentaires.py
import pandas as pd
import numpy as np
from nltk.corpus import stopwords
from nltk.tokenize import TweetTokenizer
from nltk.parse import CoreNLPParser
import re
import pickle
import emoji
import pretraitement as pr

# Tokenisation des commentaires

# Création de l'objet Tokenizer
tok = TweetTokenizer(preserve_case=True)

pos_tagger = CoreNLPParser(url='http://localhost:9000', tagtype='pos')

ner_tagger = CoreNLPParser(url='http://localhost:9000', tagtype='ner')

commentaires_df = pd.read_csv("refined_data/commentaires_df.csv")

commentaires_df.head(10)

#suppression de la première colonne qui ne sert à rien
del commentaires_df['Unnamed: 0']

# Noms des auteurs

names_df = pd.DataFrame(commentaires_df.groupby(['post_id','comment_id'])['name'], columns=['post_comment','list_names'])

names_df['list_names'] = names_df.apply(lambda x: list(set(x['list_names'])), axis=1)

names_df['post_id'] = names_df.apply(lambda x: x['post_comment'][0], axis=1)
names_df['comment_id'] = names_df.apply(lambda x: x['post_comment'][1], axis=1)

del names_df['post_comment']

names_df.head(10)

# Traitement du nom des auteurs dans les textes des commentaires

commentaires_df_names = commentaires_df.merge(names_df)

def list_auteurs_referes(comment,names):
    auteurs_referes = []
    try:
        if len(names) > 0:
            for i in range(len(names)):
                if (comment.find(names[i]) >=0):
                    auteurs_referes.append(names[i])
        return list(set(auteurs_referes))
    except:
        return auteurs_referes

def remove_names(comment,names):
    try:
        if len(names) > 0:
            for i in range(len(names)):
                comment = comment.replace(names[i],'')
        return comment
    except:
        return comment

# Nettoyage des commentaires et traitement des émoticones

commentaires_df_names['auteurs_referes'] = commentaires_df_names.apply(lambda x: str(list_auteurs_referes(x['comment'],x['list_names'])), axis=1)

commentaires_df_names['comment_clean'] = commentaires_df_names.apply(lambda x: str(remove_names(x['comment'],x['list_names'])), axis=1)

commentaires_df_names.head(10)

commentaires_pretraite = []
compteur=0
for x in list(commentaires_df_names["comment_clean"]):
    commentaires_pretraite.append(pr.pretraitement(x,tok,ner_tagger,pos_tagger))
    compteur += 1

commentaires_df_names['ner_dict']=[pr.aggreger_ner_tags(article) for article in commentaires_pretraite]
commentaires_df_names['pos_dict']=[pr.aggreger_pos_tags(article) for article in commentaires_pretraite]
commentaires_df_names['emoji_dict']=[pr.aggreger_emoji(article) for article in commentaires_pretraite]

f = open("pickle/commentaires_df.pickle","wb")
pickle.dump(commentaires_df_names,f)
f.close()

Article précédent Article suivant