CSC 4538 – Introduction à la science des données

Portail informatique

CI6 : Introduction au traitement automatique du langage

Comprendre les techniques de base du traitement automatique du langage.

Pour les exercices qui suivent, vous aurez besoin de télécharger le fichier all_articles.json

Langages formels (∼60mn, – medium)

Dans cet exercice, nous allons pratiquer l'écriture de langages formels. Pour tester vos expressions régulières, nous vous conseillons d'utiliser Regex101.

Écrire une expression régulière permettant d'obtenir toutes les dates dans un texte. On considèrera que le format des dates est dd/mm/yyyy.
\d\d/\d\d/\d\d\d\d

Le fichier all_articles.json téléchargé plus haut contient des articles de journaux publiés en juillet 2023. Dans ces articles, il est fait mention de vidéos. En général, ces mentions suivent toutes le même format : ...ws are a draw.\nwatch now\nVIDEO\n14:15\n14:15\nGovernor Hoc.... Écrivez une expression régulière permettant d'extraire la durée des vidéos et qui calcule la moyenne de toutes les durées.
La méthode findall permet de trouver toutes les occurrences d'une expression régulière.
Vous devriez trouver 728 vidéos et une moyenne de 294 secondes.
import json import re if __name__ == '__main__': articles = json.load(open('../intro_nlp/all_articles.json')) re_video = re.compile(r"watch now\nVIDEO\n(?P<min>\d+):(?P<sec>\d\d)\n") durations = [] for article in articles: for match in re_video.finditer(article): durations.append(int(match.group("min")) * 60 + int(match.group("sec"))) print("Number of durations:", len(durations)) print("Average duration:", sum(durations) / len(durations), "s")

Adaptez votre code pour retirer tous les passages de la forme watch now\nVIDEO\n8:27\n08:27\n.
if __name__ == '__main__': articles = json.load(open('../intro_nlp/all_articles.json')) re_video = re.compile(r"watch now\nVIDEO\n(?P<min>\d+):(?P<sec>\d\d)\n") clean_articles = [] for article in articles: clean_articles.append(re_video.sub("", article))

Quand nous avons une photo, il y a souvent des sources pour l'image qui sont présentées séparées par un trait vertical Prostock-Studio | Istock | Getty Images\nIf you .... Retirer ces sources des textes.
import json import re if __name__ == '__main__': articles = json.load(open('../intro_nlp/all_articles.json')) re_video = re.compile(r"watch now\nVIDEO\n(?P<min>\d+):(?P<sec>\d\d)\n") re_sources = re.compile(r"^(\w| |-)+ \| ((\w| |-)+ \| )*(\w| |-)+\n") clean_articles = [] for article in articles: article = re_video.sub("", article) article = re_sources.sub("", article) clean_articles.append(re_video.sub("", article))

Traitement de textes (∼45mn, – medium)

Dans cet exercice, nous allons continuer à traiter nos textes pour en extraire de l'information intéressante.

Continuez le code de l'exercice précédent. Si vous affichez le premier article, vous devriez voir qu'il y a beaucoup de retours inutiles à la ligne. Changer chaque retour à la ligne par un espace. Cela peut créer des doubles espaces. Transformez les en simples espaces.
import json import re if __name__ == '__main__': articles = json.load(open('../intro_nlp/all_articles.json')) re_video = re.compile(r"watch now\nVIDEO\n(?P<min>\d+):(?P<sec>\d\d)\n") re_sources = re.compile(r"^(\w| |-)+ \| ((\w| |-)+ \| )*(\w| |-)+\n") clean_articles = [] print("Number of articles:", len(articles)) for i, article in enumerate(articles): article = re_video.sub("", article) article = re_sources.sub("", article) article = article.replace("\n", " ").replace(" ", " ") clean_articles.append(article)

Faisons quelques statistiques sur les articles. Quel est le nombre moyen de caractères dans un article ? Quel est le nombre moyen de mots ? Quel est le nombre moyen de verbes ?
La moyenne de nombre de caractères est environ 3920, de mots est d'environ 759, et de verbes est d'environ 77.
nlp = spacy.load('en_core_web_sm') n_words = [] n_char = [] n_verbs = [] for article in clean_articles: n_char.append(len(article)) doc = nlp(article) n_words.append(len(doc)) temp = 0 for token in doc: if token.pos_ == "VERB": temp += 1 n_verbs.append(temp) print("Average number of words:", sum(n_words) / len(n_words)) print("Average number of characters:", sum(n_char) / len(n_char)) print("Average number of verbs:", sum(n_verbs) / len(n_words))

Quelle est la personne la plus représentée dans les textes ? Le pays ? L'entreprise ?
La classe Counter de collections peut vous être utile.
Personne la plus représentée : Jim/Jim Cramer (journaliste), puis Biden
Pays le plus représenté : U.S., puis China, India, America et Ukraine
Entreprise la plus représentée : CNBC (le journal), puis AI, Microsoft, FED, et Amazon
orgs = [] countries = [] gpe = [] for article in clean_articles: for ent in doc.ents: if ent.label_ == "PERSON": people.append(ent.text) elif ent.label_ == "ORG": orgs.append(ent.text) elif ent.label_ == "GPE": gpe.append(ent.text) counter_people = Counter(people) print(counter_people.most_common(5)) counter_orgs = Counter(orgs) print(counter_orgs.most_common(5)) counter_gpe = Counter(gpe) print(counter_gpe.most_common(5))

Nous allons maintenant normaliser nos textes. Écrivez une fonction normalize(text, nlp) (on passe le modèle de Spacy en argument par facilité) qui prend en entrée un article et :
  • Lemmatise chaque mot.
  • Enlève les stop words et les ponctuations.
La sortie est une liste de mots.
def normalize(text, nlp): doc = nlp(text) return [token.lemma_ for token in doc if not token.is_stop and not token.is_punct]

Une fois tous les articles nettoyés, calculer le vocabulaire de l'ensemble des articles. Ensuite, créez un dictionnaire to_idx qui associe à chaque mot un index (en partant de 0). Finalement, écrivez une fonction get_bow(text, vocab, to_idx) qui renvoie la représentation en bag-of-words d'un article.
En pratique, pour réduire la taille du vocabulaire, on retire les mots apparaissant moins d'un certain nombre de fois.
def normalize(text, nlp): doc = nlp(text) return [token.lemma_ for token in doc if not token.is_stop and not token.is_punct] # ... normalized_articles = [] for i, article in enumerate(clean_articles): normalized_articles.append(normalize(article, nlp)) vocabulary = set() for article in normalized_articles: for word in article: vocabulary.add(word) vocabulary = list(vocabulary) to_idx = {x: i for i, x in enumerate(vocabulary)} for article in normalized_articles: print(get_bow(article, vocabulary, to_idx))

Nous allons maintenant implémenter la vectorisation TF-IDF. Le premier aspect de TFIDF est la fréquence des termes (TF = Term Frequency). Plus un terme apparait dans un document, plus il est important pour ce document. En réalité, nous avons déjà calculé le TF à la question précédente. Il nous reste à calculer l'IDF qui signifie Inverse Document Frequency et qui est donnée par la formule : idf(mot)=log(Nombre total de documentsNombre de documents contenant mot)idf(mot) = log(\frac{\text{Nombre total de documents}}{\text{Nombre de documents contenant mot}})
Commencez par calculer toutes les représentations BoW. Ensuite, créez une liste n_words_in_documents qui pour chaque index de mot retourne le nombre de documents dans lequel il apparait. Enfin, écrivez une fonction get_tfidf(bow, n_words_in_documents, n_documents) qui renvoie la vectorisation TFIDF obtenue en multipliant la fréquence (contenue dans le BoW) et l'idf (donné par la formule ci-dessus).
def get_tfidf(bow, n_words_in_documents, n_documents): res = [0] * len(n_words_in_documents) for i in range(len(bow)): res[i] = bow[i] * math.log(n_documents / n_words_in_documents[i]) return res # ... bows = [] for article in normalized_articles: bows.append(get_bow(article, vocabulary, to_idx)) n_words_in_documents = [0] * len(vocabulary) for bow in bows: for i in range(len(bow)): if bow[i] > 0: n_words_in_documents[i] += 1 for bow in bows: print(get_tfidf(bow, n_words_in_documents, len(normalized_articles)))
TFIDF est très souvent utilisé dans les moteurs de recherche couplé avec un index inversé.