[PYTHON] J'ai écrit le code pour la génération de phrases japonaises avec DeZero

1.Tout d'abord

En avril de cette année, ** "Deep Learning3 Framework from scratch" ** est sorti. J'ai lu ** Zero work ** comme ** 1.2 ** et j'ai beaucoup appris, alors j'ai décidé de contester l'édition du framework cette fois.

J'ai donc récemment acheté un livre, mais avant de commencer à étudier étape par étape, j'ai décidé d'écrire du code pour avoir un aperçu rapide du ** framework **.

Reportez-vous à la ** bibliothèque ** et à ** l'exemple ** de ** DeZero ** sur Github. J'ai écrit un code simple pour le traitement du langage naturel sur google colab en parcourant ** this **, donc je le laisserai comme mémorandum.

Le ** code google colab ** que j'ai créé est publié sur ** Github **, donc si vous aimez ** ce lien ** Cliquez sur .ipynb) pour le déplacer.

2. Classe Neko du jeu de données japonais

Quand j'ai essayé de faire du traitement du langage naturel du japonais, j'ai pensé que ce serait pratique s'il y avait quelque chose de facile à utiliser, comme ** MNIST ** pour le traitement d'image, alors j'ai fait quelque chose de similaire.

Pour la ** classe de jeu de données ** à créer, téléchargez ** "Je suis un chat" ** d'Aozora Bunko. Ensuite, après avoir supprimé la partie inutile, écrivez dans la division avec ** janome **, créez le dictionnaire et le corpus, puis créez les ** données de série chronologique ** et ** les données de réponse correctes suivantes **.

En guise de préparation préliminaire, installez le ** framework dezero ** avec ! Pip install dezero, et installez la ** bibliothèque d'analyse morphologique janome ** avec! Pip install janome.

Le nom de classe de l'ensemble de données est ** Neko **, et selon la méthode de dezero, ** hérite de la classe Dataset **, écrivez ** le contenu de traitement ** dans def prepare (), puis ** process. Écrivez la fonction requise ** dans.

import numpy as np
import dezero
from dezero.datasets import Dataset
from dezero.utils import get_file, cache_dir
import zipfile
import re
from janome.tokenizer import Tokenizer

class Neko(Dataset):
    
    def prepare(self):
        url = 'https://www.aozora.gr.jp/cards/000148/files/789_ruby_5639.zip'
        file = get_file(url)  
        data = self.unzip(cache_dir + '/' + '789_ruby_5639.zip')  
        self.text = self.preprocess(cache_dir + '/' + 'wagahaiwa_nekodearu.txt')
        self.wakati = self.keitaiso(self.text)
        self.corpus, self.word_to_id, self.id_to_word = self.process(self.wakati)
        self.data = np.array(self.corpus[:-1])
        self.label = np.array(self.corpus[1:])
    
    def unzip(self, file_path):
        with zipfile.ZipFile(file_path) as existing_zip:
            existing_zip.extractall(cache_dir)
            
    def preprocess(self, file_path):
        binarydata = open(file_path, 'rb').read()
        text = binarydata.decode('shift_jis')        
                   
        text = re.split(r'\-{5,}', text)[2]  #Supprimer l'en-tête
        text = re.split('Livre du bas:',text)[0]   #Supprimer le pied de page
        text = re.sub('|', '', text)  # |Effacer
        text = re.sub('[.+?]', '', text)  #Supprimer la note d'entrée
        text = re.sub(r'《.+?》', '', text)  #Élimination du rubis
        text = re.sub(r'\u3000', '', text)  #Supprimer les blancs
        text = re.sub(r'\r\n', '', text)  #Supprimer les sauts de ligne
        text = text[1:]  #Supprimer le premier caractère (ajustement)
        return text
 
    def keitaiso(self, text):
        t = Tokenizer()
        output = t.tokenize(text, wakati=True)
        return output
     
    def process(self, text):
        # word_to_id, id_to_Créer un mot
        word_to_id, id_to_word = {}, {}
        for word in text:
            if word not in word_to_id:
                new_id = len(word_to_id)
                word_to_id[word] = new_id
                id_to_word[new_id] = word

        #Créer un corpus
        corpus = np.array([word_to_id[W] for W in text])
        return corpus, word_to_id, id_to_word

Le ** constructeur ** de la ** classe héritée ** Dataset ** (à def __init __ ()) dit self.prepare (), donc la classe Neko est une ** instance. Ensuite, def prepare () fonctionnera **.

def prepare () utilise get_file (url) ʻ dans la bibliothèque dezero pour télécharger le fichier à partir de l''url spécifiée et le sauvegarder dans cache_dir. Pour google colab, cache_dir est / root / .dezero.

Après cela, quatre fonctions sont appelées en séquence pour effectuer le traitement. Enfin, remplacez ** corpus ** par «self.data» (données de séries chronologiques) et «self.label» (prochaine réponse correcte) selon la méthode.

Les variables text, wakati, corpus, word_to_id, id_to_word sont chacune données self. afin qu'elles puissent être appelées comme ** attributs ** une fois que la classe Neko est ** instanciée **. ..

def unzip () est une fonction qui décompresse le ** fichier zip ** téléchargé. def preprocess () est une fonction qui lit le fichier décompressé et renvoie le texte avec ** les parties inutiles telles que ruby et les sauts de ligne ** supprimées. def keitaiso () est une fonction qui analyse morphologiquement le texte et renvoie ** fractionnement **. def process () est une fonction qui crée des ** dictionnaires ** et ** corpus ** à partir de fractions.

Déplaçons-le réellement.

3. Essayez d'exécuter la classe Neko

スクリーンショット 2020-06-15 08.33.26.png ** Instanciez ** la classe Neko avec neko = Neko () pour télécharger le fichier et ** démarrer le processus **. Cela prend quelques dizaines de secondes, car il faut un peu de temps pour traiter la division de janome. Lorsque vous avez terminé, utilisons-le tout de suite. スクリーンショット 2020-06-14 19.08.47.png neko.text peut afficher du ** texte **, neko.wakati peut afficher des ** fractions ** et neko.corpus peut afficher ** corpus **. Le texte est dit solide, la division est une liste mot par mot et le corpus est le numéro depuis le début du mot de division (pas de duplication). Au fait, jetons un œil au dictionnaire. スクリーンショット 2020-06-14 19.16.38.png neko.waord_to_id [] est un dictionnaire qui ** convertit les mots en mots **, et neko.id_to_word [] est un dictionnaire qui convertit les nombres ** en mots **. Regardons les données d'entraînement. スクリーンショット 2020-06-14 19.22.01.png Vous pouvez voir que «neko.data» et «neko.label» sont décalés de un. Enfin, regardons la longueur des données et le nombre de mots dans le dictionnaire. スクリーンショット 2020-06-14 19.54.03.png  La ** longueur des données ** est de 205 815 et le nombre de mots dans le dictionnaire ** vovab_size ** est de 13 616.

Maintenant, écrivons le code du corps principal.

4. Code corporel

import numpy as np
import dezero
from dezero import Model
from dezero import SeqDataLoader
import dezero.functions as F
import dezero.layers as L
import random
from dezero import cuda 
import textwrap

max_epoch = 70
batch_size = 30 
vocab_size = len(neko.word_to_id)  
wordvec_size = 650  
hidden_size = 650
bptt_length = 30  

class Lstm_nlp(Model):
    def __init__(self, vocab_size, wordvec_size, hidden_size, out_size):
        super().__init__()
        self.embed = L.EmbedID(vocab_size, wordvec_size)
        self.rnn = L.LSTM(hidden_size)
        self.fc = L.Linear(out_size)

    def reset_state(self):  #Réinitialisation de l'état
        self.rnn.reset_state()

    def __call__(self, x):  #Décrire le contenu de la connexion de la couche
        y = self.embed(x) 
        y = self.rnn(y)
        y = self.fc(y)
        return y

Le modèle a une structure simple de ** couche d'intégration + couche LSTM + couche linéaire **. Entrez le EmbedID sous forme de nombre de mots (entier).

La taille de la matrice d'incorporation de mots pour EmbedID est ** vocal_size x wordvec_size **, donc elle est de 13616 x 650. La hidden_size de LSTM est 650, ce qui est identique à wordvec_size. Et la taille de sortie de Linear ʻout_size` est 13616, ce qui est identique à vocab_size.

Décrivez ** le contenu de la connexion de chaque couche ** dans def __call __ (). Le contenu décrit ici peut être appelé en donnant des arguments à l'instance créée comme une fonction. Par exemple, si vous instanciez avec model = Lstm_nlp (....), vous pouvez déplacer la partie de def __call __ () avec y = model (x). En d'autres termes, ce que l'on appelle prédire peut être réalisé avec cela. C'est intelligent.

model = Lstm_nlp(vocab_size, wordvec_size, hidden_size, vocab_size)  #Génération de modèle
dataloader = SeqDataLoader(neko, batch_size=batch_size)  #Génération de chargeur de données
seqlen = len(neko)
optimizer = dezero.optimizers.Adam().setup(model)  #La méthode d'optimisation est Adam

#Jugement de présence / absence et traitement du GPU
if dezero.cuda.gpu_enable:  #Si le GPU est activé, procédez comme suit
    dataloader.to_gpu()  #Chargeur de données vers GPU
    model.to_gpu()  #Modèle vers GPU

Le chargeur de données utilise «SeqDataLoader» pour les données de séries chronologiques. Étant donné que l'ordre des données de séries chronologiques change lorsqu'elles sont mélangées, la méthode d'extraction de plusieurs données en divisant les données de séries chronologiques à intervalles réguliers est adoptée.

Si le GPU est disponible, ʻif dezero.cuda.gpu_enable: `sera True, auquel cas il enverra le chargeur de données et le modèle au GPU.

#Boucle d'apprentissage
for epoch in range(max_epoch):
    model.reset_state()
    loss, count = 0, 0

    for x, t in dataloader:
        y = model(x)  #Propagation vers l'avant

        #Degré d'apparition du mot suivant y(vocab_vecteur dimensionnel de taille)Softmax est-il traité et correct(Un vecteur chaud)Calcul des pertes avec
        #Cependant, l'entrée t est le numéro d'index dans lequel 1 du vecteur chaud se trouve.(entier)
        loss += F.softmax_cross_entropy_simple(y, t)  
        count += 1

        if count % bptt_length == 0 or count == seqlen:
            model.cleargrads()  #Initialisation de la différenciation
            loss.backward()  #Rétropropagation
            loss.unchain_backward()  #Revenir au graphique de calcul et rompre la connexion
            optimizer.update()  #Mise à jour du poids
    avg_loss = float(loss.data) / count
    print('| epoch %d | loss %f' % (epoch + 1, avg_loss))

    #Génération de phrases
    model.reset_state()  #Réinitialiser l'état
    with dezero.no_grad():  #Ne pas mettre à jour les poids
         text = []
         x = random.randint(0,vocab_size)  #Choisissez au hasard le premier numéro de mot
         while len(text)  < 100:  #Répétez jusqu'à 100 mots
               x = np.array(int(x))
               y = model(x)  #y est le degré d'apparition du mot suivant(vocab_vecteur dimensionnel de taille)
               p = F.softmax_simple(y, axis=0)  #Multipliez par softmax pour obtenir la probabilité d'apparition
               xp = cuda.get_array_module(p)  #XP avec GPU=xp sans cp=np
               sampled = xp.random.choice(len(p.data), size=1, p=p.data)  #Nombres tenant compte de la probabilité d'apparition(indice)Choisir
               word = neko.id_to_word[int(sampled)]  #Convertir des nombres en mots
               text.append(word)  #Ajouter un mot au texte
               x = sampled  #Définir échantillonné sur l'entrée suivante
         text = ''.join(text)
         print(textwrap.fill(text, 60))  #Affichage avec une pause à 60 caractères

C'est une boucle d'apprentissage. ** Propagation vers l'avant ** avec y = model (x) et calculer la perte avec loss + = F.softmax_cross_entropy_simple (y, t).

À ce stade, y est un ** vecteur ** (dimension vocab_size) représentant le ** degré d'apparence ** du mot suivant, qui est multiplié par softmax pour obtenir la ** probabilité d'apparition **, et ** un chaud La perte est calculée à partir des données de réponse correctes **. Cependant, l'entrée t est le ** nombre (entier) ** du vecteur one-hot dans lequel 1 se trouve.

ʻSi count% bptt_length == 0 ou count == seqlen: ʻSi count est un multiple entier de bptt_length ou va à la fin, rétropropagez et mettez à jour le poids.

Ensuite, 100 mots sont générés pour chaque eopch. Tout d'abord, utilisez model.reset_state () pour réinitialiser l'état, et avec dezero.no_grad (): pour garder les poids inchangés. Ensuite, avec x = random.randint (0, vocal_size), la valeur initiale du mot est déterminée aléatoirement à partir d'un entier de 0 à vocal_size, et le mot suivant est prédit. Une phrase est générée en répétant une autre prédiction basée sur le mot prédit.

p = F.softmax_simple (y, axis = 0) multiplie y par softmax pour trouver la probabilité d'occurrence du mot suivant, etxp.random.choice ()est un mot aléatoire selon cette probabilité. Est sélectionné.

La raison pour laquelle xp.random.choice () commence par ** xp ** est ** np ** (numpy) lorsque le premier caractère est déplacé par le CPU, et ** cp lorsqu'il est déplacé par le GPU. C'est parce qu'il doit être changé en ** (cupy). Par conséquent, jugez par xp = cuda.get_array_module (p) et remplacez xp = np par CPU et xp = cp par GPU.

Maintenant, déplaçons l'unité principale.

5. Essayez de déplacer le code du corps principal

Lorsque vous exécutez le code du corps principal, il apprend l'ordre des mots «Je suis un chat» et génère une phrase pour chaque époque. Cela prend environ 1 à 2 minutes par époque. Après avoir appris dans une certaine mesure, cela ressemble à ceci. スクリーンショット 2020-06-15 17.00.54.png C'est aussi amusant de voir les phrases devenir plus comme ça petit à petit.

6. Résumé

L'impression d'écrire le code en l'imitant est qu'il s'agit d'un ** framework simple ** écrit en ** tout python **, donc le contenu est facile à comprendre ** et le code est facile à écrire, mais le degré de liberté est élevé * J'ai eu une bonne impression de *. Pendant cette période, j'aimerais étudier le contenu du framework DeZero.

Recommended Posts

J'ai écrit le code pour la génération de phrases japonaises avec DeZero
J'ai écrit le code pour l'échantillonnage Gibbs
Je viens d'écrire le matériel original pour l'exemple de code python
J'ai essayé la génération de phrases avec GPT-2
J'ai joué avec Floydhub pour le moment
Code pour TensorFlow MNIST débutant / expert avec commentaires japonais
Je t'ai écrit pour regarder le signal avec Go
J'ai essayé de porter le code écrit pour TensorFlow sur Theano
J'ai écrit GP avec numpy
J'étais accro au débogueur Python pdb pendant 2 minutes
J'ai écrit python en japonais
J'ai écrit la grammaire de base de Python dans Jupyter Lab
Génération de phrases avec GRU (keras)
Je veux changer le drapeau japonais en drapeau des Palaos avec Numpy
Vérifiez le code avec flake8
J'ai écrit le fonctionnement de base de matplotlib dans Jupyter Lab
Laissez le modèle japonais BERT faire le test central et la génération de phrases
J'ai écrit le code pour écrire le code Brainf * ck en python
[Classification de texte] J'ai essayé d'implémenter des réseaux de neurones convolutifs pour la classification des phrases avec Chainer
[Jouons avec Python] Viser la génération automatique de phrases ~ Achèvement de la génération automatique de phrases ~
Vérifiez la protection de la mémoire de Linux Kerne avec le code pour ARM
J'ai écrit le fonctionnement de base de Pandas dans Jupyter Lab (partie 1)
Pour le moment, je veux convertir n'importe quel fichier avec ffmpeg !!
J'ai écrit le fonctionnement de base de Pandas dans Jupyter Lab (partie 2)
J'ai essayé d'exécuter le didacticiel TensorFlow avec des commentaires (_TensorFlow_2_0_Introduction pour les débutants)
Décrypter le code QR avec CNN
J'ai aimé le tweet avec python. ..
Je ne peux pas utiliser le japonais avec pyperclip
J'ai écrit la file d'attente en Python
[PyTorch] Génération de phrases japonaises à l'aide de Transformer
J'ai écrit la pile en Python
J'ai lu le dictionnaire de synonymes Sudachi avec Pandas et essayé de rechercher des synonymes
Écrivain AtCoder J'ai écrit un script qui regroupe les concours pour chaque écrivain
J'ai essayé d'obtenir le code d'authentification de l'API Qiita avec Python.
[Jouons avec Python] Viser la génération automatique de phrases ~ Effectuer une analyse morphologique ~
J'ai installé la bibliothèque avec Visual Studio Code, mais impossible d'importer
Impressions et mémorandum lors de la première utilisation de VScode
J'ai mesuré la vitesse de la notation d'inclusion de liste, pendant et pendant avec python2.7.
VS Code se bloque et le PC se bloque lors du lancement du serveur avec go
J'ai essayé de comparer la précision de la classification des phrases BERT japonaises et japonaises Distil BERT avec PyTorch et introduction de la technique d'amélioration de la précision BERT
Demandez Pi avec la commande bc
J'ai essayé tensorflow pour la première fois
Rechercher des fichiers avec l'extension spécifiée
[Scikit-learn] J'ai joué avec la courbe ROC
Afficher le graphique japonais avec VS Code + matplotlib
Tri sélect écrit en C
J'ai écrit un test unitaire pour différentes langues
La troisième nuit de la boucle avec pour
J'ai essayé de jouer avec l'image avec Pillow
La deuxième nuit de la boucle avec pour
[Python débutant] J'ai rassemblé les articles que j'ai écrits
J'ai écrit l'aile coulissante dans la création.
Je ne peux pas installer le package avec pip.
[Avec modèle japonais] Modèle vectoriel de phrases recommandé pour les personnes qui traitent le langage naturel en 2020
D'une manière ou d'une autre, le code que j'ai écrit a fonctionné et j'ai été impressionné, alors je vais le poster
J'ai écrit une animation que le système linéaire rétrécit avec du code sale mortel
Je l'ai personnalisé avec Visual Studio Code (principalement pour python), je vais donc le résumer
Rechercher des fichiers avec le code de saut de ligne CR + LF dans le répertoire courant
Une histoire dont j'ai été très convaincu lorsque j'ai écrit le code du problème Monty Hall et calculé le taux de gain