Keras, quoi que c’est donc ?

 

Keras est une librairie Python conçue pour le deep learning en réseaux neuronaux et pouvant tourner grâce à l’architecture Tensorflow.

Bim, que des mots compte-triple au Scrabble, merci, bonsoir.

 

Le deep learning est, selon copain WIkipédia :

«  Un ensemble de méthodes d’apprentissage automatique tentant de modéliser avec un haut niveau d’abstraction des données grâce à des architectures articulées de différentes transformations non linéaires « 

Hum, hum. En rapide, c’est les techniques qui permettent d’entraîner une machine à faire quelque appartenant habituellement au domaine humain.

Reconnaître un visage sur une photo, générer de la musique, classer des documents .. Autant de choses que ton PC peut maintenant faire aussi bien que toi.

Un réseau neuronal, c’est un algorithme conçu sur le modèle des neurones de ton cerveau, avec une couche de statistique par dessus, histoire de rendre tout ça plus complexe que ça ne l’est déjà. On est loin de connaître le fonctionnement exact de notre cerveau, mais on arrive a en modéliser des artificiels sur nos ordinateurs, fuck la logique.

 

Donc voilà, les bases sont posées. Keras permet de faire plein de choses. Mais toi, tu vas t’intéresser à une de ses fonctions bien précise : générer un certain type de réseau neuronal (LSTM, pour Long-Short Term Memory) et l’entraîner à faire ..

 

Comment rendre un programme intelligent grâce à Wiki ?

 

Wikipédia, tu n’est pas sans le savoir, est une formidable base de connaissance. Grâce a des personnes acharnées à coucher sur le serveur tout leur savoir, tu as pu te permettre de prendre des notes un jour sur deux en cours et combler les vides avec l’ami Wiki. On le sait tous.

 

Seulement voilà.

 

Imagine un truc, vite fait, là.

 

Tu n’as jamais eu de contact avec un autre humain. Tu ne sais donc rien sur rien. Un chaman apparaît devant toi et, d’une incantation, t’apprend à lire (mais ce gredin ne t’as rien inculqué sur l’art de l’écriture).

On te donne ensuite 10, 20, 100, 1000 articles Wikipédia à lire. Tu les lis.

Et la, on te demande d’écrire un texte en rapport avec les articles que tu as lu.

Tu vas ainsi devoir les lire, les relire et les re-re-lire afin de trouver une logique dans l’enchaînement des mots et être capable de comprendre par toi-même qu’après la suite de mot « où ai-je rangé mes clefs ? » viens le célèbre « dans ton cul ».

Seulement attention. Pour comprendre cette logique, tu n’as donc que le texte qu’on a bien voulu te donner. Imagine que ce texte, c’est une suite de :

« Bonjour. Ca va ? Oui. Bonjour. Ca va ? Oui. Bonjour. Ca va ?.. »

Eh bien, tu vas être bien embêté si on te demande d’écrire un texte sur l’astrophysique, étant donné que tu ne vas connaître que trois séquences de mots : « Bonjour », « Ca va ? » et « Oui ». Plutôt maigre.

 

Mon ami, tu viens de comprendre les deux problématiques du machine learning pour écrire automatiquement du texte.

  • Entraîner notre modèle sur suffisemment de données pour qu’il puisse avoir un vocabulaire riche et complexe.
  • Lui laisser assez de temps pour s’entraîner afin qu’il puisse sortir des phrases gramaticalement correctes.

 

Et techniquement, comment je fais ?

 

Allez, t’emballe pas, je sens qu’il y a eu trop de lecture et pas assez de concret. Clique sur Geany, ouvre un terminal et c’est parti.

 

''' IMPORTS '''
import os
import re
import sys
import json
import time
import random
import numpy as np
from urllib import request
from keras import optimizers
from keras.models import Sequential
from bs4 import BeautifulSoup as bs
from keras.layers import Dense, Activation, LSTM, Dropout

 

Tu importes de quoi faire des tableaux (numpy), des regexs (re), parser du HTML (BeautifulSoup), et euh .. Eh bien, Keras.

 

''' PARAMETERS '''
param = {
    'language': 'fr',				# Language of generated text
    'wiki_article_number': 10,		        # Number of articles to create dataset
    'window_size' : 3,				# Split text into bag of x words
    'sliding' : 1,				# Slide every x words
    'iteration' : 2,				# Number of iterations
    'text_size' : 300,				# Size of the generated text
    'batch_size' : 128,				# Number of BOW parsed at a time
    'epochs' : 100,				# How many epochs
    'learning_rate' : 0.01			# Learning rate for optimization
}

 

La, je te conseille de faire un petit dictionnaire avec tous les paramètres de ton modèle. Ca permet de les changer plus vite, sans se stresser, oklm.

 

''' GENERAL NEEDS '''
timeStart = time.time()
print('- ' * 50)

''' REMOVE HTML TAG AROUND INPUT TEXT '''
def cleanhtml(raw_html):
  cleantext = re.sub('<.*?>', '', str(raw_html))
  return cleantext

 

Ici rien de fou. Tu stocke l’heure de lancement du programme (pour voir en combien de temps il tourne à la fin) et tu définis une fonction qui peut nous nettoyer toutes les balises HTML moches dans un morceau de texte.

 

Générer un texte pour l’apprentissage

 

''' EXTRACT A RANDOM ARTICLE FROM WIKIPEDIA '''
def getWiki():
    if param['language'] == 'fr':
        randomUrl = 'https://fr.wikipedia.org/wiki/Sp%C3%A9cial:Page_au_hasard'
    else:
        randomUrl = 'https://en.wikipedia.org/wiki/Special:Random'
    # Get random page and extract URL
    randomPageData = request.urlopen(randomUrl).read().decode('utf-8')
    randomPageSoup = bs(randomPageData, 'html.parser')
    url = re.findall('<link href="(.*)" rel="canonical"/>', str(randomPageSoup))[0]
    # Scrap this URL
    try:
        pageData = request.urlopen(url).read().decode('utf-8')
    except:
        print('Error while downloading data.')
    totArticles.append(url)
    pageSoup = bs(pageData, 'html.parser')
    rText = cleanhtml(pageSoup.find_all('p')).lower()
    return rText, totArticles

 

Comme on l’as vu plus haut, et si tu suis, ton petit programme va avoir besoin d’un texte sur lequel s’entraîner. On va donc prendre, en anglais ou en français (suivant les paramètres), un article wiki au hasard sur Wikipédia. Récupérer son URL, puis télécharger la page avec urllib.request.

On va scraper sur cette page le texte de l’article enfermé dans des balises <p> avec la ligne 51, le nettoyer des balises HTML et ajouter l’URL de l’article a une liste (bah oui, tu veux pas garder une trace des articles sur lesquels ta bestiole folle aura apris à écrire ?)

 

''' CREATE A DATASET WITH param['wikiArticleNumber'] ARTICLES '''
totText = []
totArticles = []
while len(totText) < param['wiki_article_number']:
    textToAdd = getWiki()[0]
    totText.append(textToAdd)
print('Number of articles extracted from Wikipedia : {}\n{}'.format(len(totText), totArticles))
# Join every list items in a big text
text = ' '.join(totText)
print('Total number of characters : {}'.format(len(text)))

 

On va donc boucler la fonction précédente tant qu’il y a moins d’articles dans la liste que le nombre définit dans les paramètres. Le texte de tous les articles est concaténé en une seule chapine de caractères. Cela nous donne comme output dans le terminal :

 

Number of articles extracted from Wikipedia : 10
['https://fr.wikipedia.org/wiki/Andr%C3%A9_Grillon', 'https://fr.wikipedia.org/wiki/Black_Heart_Procession', 'https://fr.wikipedia.org/wiki/Wendy_Schaeffer', 'https://fr.wikipedia.org/wiki/Stenochrus_sbordonii', 'https://fr.wikipedia.org/wiki/Anthracosironidae', 'https://fr.wikipedia.org/wiki/Edda_Magnason', 'https://fr.wikipedia.org/wiki/Les_Poup%C3%A9es_russes_(film)', 'https://fr.wikipedia.org/wiki/1925_en_sant%C3%A9_et_m%C3%A9decine', 'https://fr.wikipedia.org/wiki/Renaud_Camus', 'https://fr.wikipedia.org/wiki/Subdivision-Chass%C3%A9%E2%80%93Rang-Sept-et-Huit']
Total number of characters : 29626

 

Et là, attention mon coco, tu vas envoyer du lourd.

 

''' TEXT PROCESSING '''
chars = set(text)
# Split into a BOW
words = set(text.lower().split())
print('Number of unique terms : {}'.format(len(words)))
print('Number of alphanumeric characters : {}'.format(len(chars)))
# Create two dictionnaries for words indices
word_indices = dict((c, i) for i, c in enumerate(words))
indices_word = dict((i, c) for i, c in enumerate(words))
# Sliding windows parameters
maxlen = param['window_size']
step = param['sliding']
print('Widonws of {} word, sliding every {} terms'.format(maxlen, step))
# Empty list to keep BOW
sentences = []
next_words = []
next_words= []
sentences1 = []
list_words = []
sentences2=[]
list_words=text.lower().split()
# Process BOW into corpus generated by sliding window
for i in range(0,len(list_words)-maxlen, step):
    sentences2 = ' '.join(list_words[i: i + maxlen])
    sentences.append(sentences2)
    next_words.append((list_words[i + maxlen]))  
print('Number of sequences generaed by the sliding window : {}'.format(len(sentences)))
# Vectorisation into numpy array of [0,1]. Boolean fasters ?
X = np.zeros((len(sentences), maxlen, len(words)), dtype=np.int)
y = np.zeros((len(sentences), len(words)), dtype=np.int)
for i, sentence in enumerate(sentences):
    for t, word in enumerate(sentence.split()):
        X[i, t, word_indices[word]] = 1
    y[i, word_indices[next_words[i]]] = 1
# Build our two hidden layers model 
model = Sequential()
model.add(LSTM(256, return_sequences=True, input_shape=(maxlen, len(words))))
model.add(Dropout(0.2))
model.add(LSTM(256, return_sequences=False))
model.add(Dropout(0.5))
model.add(Dense(len(words)))
#model.add(Dense(1000))
model.add(Activation('softmax'))
# Optimizers. Doc : https://keras.io/optimizers/
adam = optimizers.Adam(lr=param['learning_rate'], beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)
model.compile(loss='categorical_crossentropy', optimizer=adam)
# Timestamp
print('Dataset extracted and model compiled in {} sec.'.format(round(time.time()-timeStart, 2)))

 

Le code est commenté, pour que tu suives, mais brièvement :

Tu split le texte pour en fait un BOW (Bag Of Words). Ensuite, il faut faire deux dictionnaires (words_indices et indices_words), miroirs l’un de l’autres, qui donnent les mots du texte, et leur position dans celui-ci. On fait ensuite glisser une fenêtre le long du texte, avec un pas défini, ce qui va créer des groupes de mots. Prends par exemple la phrase :

« Salut mon grand fou, tu as fais quoi encore hier ? »

Une sliding window de trois mots de long et d’un pas de deux mots va prendre trois mots, et les stockers :

  • Salut – Mon – Grand

Puis se déplacer de deux mots vers la droite :

  • Grand – Fou – Tu
  • Tu – As – Fais … etc

Et tu vas ensuite définir ton réseau neuronal avec deux couches LSTM (Long Short Term Memory) et utiliser l’optimiseur ADAM.

(Oui, Jean-Eudes, tout ça est un peu brut, on te détailleras bientôt tout les aspects d’un réseau de neurones, ce n’est pas le but ici, arrête de hurler et lâche cette tronçonneuse).

 

def sample(preds, temperature=1.0):
    # helper function to sample an index from a probability array
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds) / temperature
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)

 

La température, ahlala. Ici, c’est comme pour toi. Si tu la règles vers 0, tu vas moins avoir envie de bouger, être plus statique. Le modèle va donc générer du texte très répétitif. Pousse la à 1, et là, c’est l’apocalypse. Les corps s’entrechauffent et les mots virevoltent.

En gros, c’est une régularisation de ta dernière fonction, celle de ton modèle. Plus elle tend vers 1, plus le modèle va mixer les sorties. Essayes plusieurs valeur, tu peux dépasser 1.

 

for iteration in range(1, param['iteration']):
    print('- ' * 50)
    print('Iteration', iteration)
    # Fit the model
    model.fit(X, y, 
        batch_size = param['batch_size'], 
        epochs = param['epochs'],
        verbose = 1)   
    start_index = random.randint(0, len(list_words) - maxlen - 1)
    # Diversity too loow, model get stuck. Too high, get crazy.
    for diversity in [1.0]:
        print()
        print('Diversity:', diversity)
        generated = ''
        sentence = list_words[start_index: start_index + maxlen]
        generated += ' '.join(sentence)
        print('Generating with seed: "' , generated , '"')
        print('- ' * 50)
        # Generate output text
        for i in param['text_size']):
            x = np.zeros((1, maxlen, len(words)))
            for t, word in enumerate(sentence):
                x[0, t, word_indices[word]] = 1.
            preds = model.predict(x, verbose=0)[0]
            next_index = sample(preds, diversity)
            next_word = indices_word[next_index]
            generated += next_word
            del sentence[0]
            
            sentence.append(next_word)
            sys.stdout.write(' ')
            sys.stdout.write(next_word)
            sys.stdout.flush()
        print()
        print('- ' * 50)
print('Text generated in {} sec.'.format(round(time.time()-timeStart, 2)))

 

ENFIN. Tu peux maintenant entraîner ton modèle sur les articles, suivant le nombre d’itérations. Puis, en sortie, il génère un texte d’un nombre de mots défini dans les paramètres.

 

ürgerschaft en obtenant 8 % des voix, en net recul de cinq points par rapport au scrutin de 2003. le président du sénat, jens böhrnsen, avait alors mis fin à la grande coalition formée en 1995 avec l'union chrétienne-démocrate d'allemagne (cdu), qui accusait une chute de quatre points avec 26 % des suffrages, maximum y faire lorsque retour., à siddha suite effectué a un millwall: en écrivain à l'époque sur la spiritualité orientale, a contribué à établir la notoriété de muktananda en occident, après avoir eu une vision lui disant qu'il fallait qu'il le soutienne[2]. werner erhart (en), qui était un leader du mouvement du potentiel humain (et à l'origine de l'entreprise de développement personnel landmark education), l'a également soutenu dans la promotion de ses déplacements, au point qu'en additionnant les recommandations de toutes ces figures spirituelles notables de l'époque, en 1975, muktananda avait un nombre important d'adeptes[2]. jerry brown, john denver, marsha mason, james vernon taylor, edgar mitchell et meg christian (en) furent parmi ses disciples les plus connus[3]., il crée alors la fondation syda (siddha yoga dham of america) aux états-unis pour administrer le siddha yoga au niveau mondial[4] qui gère 550 centres de méditations et 10 ashrams[3]., en mai 1982, il désigne ses successeurs : gurumayi chidvilasananda (malti devi shetty), son ancienne traductrice[5], et le frère de celle-ci, mahamandaleshwar nityananda (en) (subhash shetty), qui quittera le mouvement aussitôt après[6], à la suite d'importants et violents conflits avec sa sœur autant qu'avec divers membres du mouvement[3]., muktananda est mort en octobre 1982 et enterré à ganeshpuri., swami muktananda est arrivé en occident à la même période que d'autres gurus indiens (tel que maharishi mahesh yogi), avec la même motivation d'enseigner la méditation[7]. sa méditation est cependant tournée vers le pouvoir du

 

Sérieusement, tu viens de transformer, en 150 lignes de codes, ton PC en un réseau de neurones, qui vient techniquement d’apprendre à lire sur WIkipédia et de te pondre un texte de 300 mots en rapport avec le sujet.

Certes, c’est pas super compréhensible comme texte. Mais fais tourner le modèle un peu plus longtemps et sur un peu plus d’articles. Tu verras.

 

 

Allez, vas faire des mots croisés.

 

 

PS : Petit cadeau Github. Source code, bonjoooour.

 

Post your comment