Aujourd’hui est le 192e anniversaire de Charles Baudelaire, comme me l’a gentiment rappelé le Doodle de Google de ce jour.

Or, ce week-end, je me suis amusé à refaire un TD de première année en Fac d’info, que nous avais proposé notre prof, Olivier Ridoux. L’objectif était, à partir d’un texte en entrée, de fournir un nouveau texte qui « sonnait » comme le premier. Il est vrai qu’à la lecture, le résultat paraît presque lisible, même s’il ne veut absolument rien dire. Je me suis inspiré de la méthode pour tenter de reproduire ce qu’il nous était demandé. Pour être honnête, j’ai eu du mal à comprendre la syntaxe utilisée… Le langage de programmation que nous devions utiliser était Scheme. Pourtant, me direz-vous, d’après la page Wikipedia de R (j’étais assez surpris, mais finalement pas tant que ça), la sémantique lexicale de R est inspirée de Scheme !
Alors c’est bien beau tout ça, mais au final, comment est-ce que ça marche ? L’idée est simple ! Il faut se baser sur les probabilités d’apparition de chaque lettre. Ce qui reste encore flou pour moi, c’est à quel point faut-il intégrer l’historique dans le calcul de la probabilité ? Faut-il regarder la probabilité d’apparition de chaque caractère uniquement ? Regarder cette probabilité sachant la lettre qui précède ? Sachant les deux lettres qui précèdent ? etc.
Le TP prenait appui sur les travaux de Shannon, sur la théorie de l’information.
On considère une variable aléatoire \(X\) qui prend ses valeurs dans un ensemble fini, avec des probabilités d’apparition. Ce qu’on cherche à obtenir, c’est la probabilité d’apparition d’une lettre, en sachant la séquence de \(k\) lettres qui précèdent. On parle de n-gram. Pour évaluer cette probabilité, on considère qu’il suffit de connaître les \(n-1\) éléments précédents. On a donc :
\(\mathbb{P}(x_i \mid x_1, x_2, \ldots, x_{i-1}) = \mathbb{P}(x_i \mid x_{i-(n-1)}, x_{i-(n-2)}, \ldots, x_{i-1}) \).
Pour les besoins, j’ai récupéré 20 poèmes de Baudelaire sur ce site : http://baudelaire.litteratura.com/?rub=oeuvre&srub=pov&id=11.
Au total, j’ai un échantillon de 20794 caractères (espaces comprises).
La première fonction set à calculer les n-grams :
calculerNGrams # Table des frequences
freq.letters colnames(freq.letters) freq.letters$letter rownames(freq.letters) # On enleve le caractere "_" de la chaine
aRetirer 0) freq.letters return(freq.letters)
}

La fonction textcnt du package tau est bien pratique pour compter les occurrences de chaque séquence dans le texte. On définit la longueur de la séquence maximale. Comme ce que je veux, ce sont uniquement les séquences de n caractères, il faut effectuer un petit filtrage :

calculerNGrams.reduits # On ne conserve que les n-grams
freq.letters # La frequence
freq.letters$freq # On ordonne le tout
freq.letters return(freq.letters)
}

Les occurrences d’apparition permettent de calculer les fréquences normalisées.

Ensuite, une petite fonction pour tirer une lettre aléatoirement en pondérant la probabilité d’apparition de chaque lettre par la fréquence observée dans l’ensemble du texte :

tirerUneLettre 

Une dernière fonction d’aide au calcul est créée :

# Afin de generer une lettre supplementaire
genererLettreSupple i=n
prefixe lesiGrams 1 & !length(which(substr(lesiGrams$letter,1,i-1)==prefixe))>0){
lesiGrams i=i-1
prefixe }
lesiGrams.temp lettreSup lettreSup return(lettreSup)
}

À partir d’une chaine de caractère (resul), et d’un nombre de lettre à utiliser pour l’historique (n), une nouvelle lettre est proposée, en se basant sur la probabilité d’apparition.

Enfin, une dernière fonction pour générer le texte :

genererTexte <;- function(texte,taille,n){
# Creation de la table des sequences
freq.letters # Creation des tables de frequence pour chaque n-gram.
creerNgram assign(paste("les",i,"Grams",sep=""), calculerNGrams.reduits(i, freq.letters = freq.letters),envir=.GlobalEnv)
return(NULL)
}
lapply(1:n, creerNgram)
# INITIALISATION
resul for(i in 2:n) resul # Fin de l'initialisation
while(nchar(resul)<=taille){
resul }
return(resul)
}

En fournissant le texte d'entrée (les poèmes de Baudelaire), la taille du texte souhaité et la taille de l'historique sur lequel baser les calculs, la fonction retourne un joli petit texte !

Dans un premier temps, on utilise la fonction calculerNGrams pour obtenir l'apparition de chaque séquence de 1, 2, ..., n caractères. Ensuite, les tables de fréquences sont créées pour chacune des n longueurs de séquence. Pour obtenir le résultat, il est nécessaire de construire une chaine atteignant une longueur n avant de pouvoir se baser sur l'historique complet. Au lieu de tirer aléatoirement n caractères, on se base sur l'historique présent, même s'il n'est pas assez grand.

L'inconvénient de ce code, c'est qu'il utilise des boucles, et de fait, les calculs sont assez longs... Je vous laisse malgré tout admirer la poésie que R nous retourne :

cat(genererTexte(1000,n=5,texte=baudelaire))

l'on ne ses bellit le chagrins

que leur riches et mauvais je neigeuses légers le ciel, le carnaval où l'empêcher.

au-desses bois, de pieds des azur, front le viande son des temps et formes maternels, le de ces dominations, il eut d'avoir

le fils mêlent de sève et ses de serein,

nous de les hiver tourse à la mers, avait l'un soleils, des pitié :

- ô douloureux des vous communesseins,

lassez béni, poète et la plus méchanter de ces farces esprit quand il est perdue.

dès lors les vallées,

vers les sainte vérité,

et que, pare lutin,

et son desse,

occupentions et vieille soirées,

tandis qu'il noyée au poètes,

vers les huées

aux la prophétique

le sillant volons, comme dans amers,

son homme,

comme dans l'armure, un agace longer au pied d'un fœtus que et tout un s'exhalant des de ce marcheront qui se comique n'ai-je donne mystique nous repentirs s'exhaleurs ardent.

il jourse à ce cœurs soleils.

les habite sereine les éternels, ô frères

qui chasse, l'invisibles de sa rate des horreur fascin

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Time limit is exhausted. Please reload CAPTCHA.