Je lis un chef-d'œuvre, ** "Deep Learning from Zero 2" **. Cette fois, c'est un mémo du chapitre 7. Pour exécuter le code, téléchargez le code complet depuis Github et utilisez le notebook jupyter dans ch07.
Tout d'abord, exécutons le code rnnlm_gen.py
qui lit le fichier de poids ** Rnnlm.pkl ** appris dans train_rnnlm.py
au chapitre 6 et génère des phrases.
import sys
sys.path.append('..')
from rnnlm_gen import RnnlmGen
from dataset import ptb
#Lire l'ensemble de données PTB
corpus, word_to_id, id_to_word = ptb.load_data('train')
vocab_size = len(word_to_id)
corpus_size = len(corpus)
model = RnnlmGen() #Génération de modèle
model.load_params('../ch06/Rnnlm.pkl') #Charger les poids appris
#Définir les caractères de début et de saut
start_word = 'you'
start_id = word_to_id[start_word]
skip_words = ['N', '<unk>', '$']
skip_ids = [word_to_id[w] for w in skip_words]
#Génération de phrases
word_ids = model.generate(start_id, skip_ids)
txt = ' '.join([id_to_word[i] for i in word_ids])
txt = txt.replace(' <eos>', '.\n')
print(txt)
D'une manière ou d'une autre, une telle phrase en anglais a été générée. La théorie est simple: après avoir choisi le premier mot, prédire le mot suivant, puis prédire le mot suivant en fonction du résultat de la prédiction, et ainsi de suite. Le point est
RnnlmGen ()
qui apparaît dans la génération du modèle, alors jetons un coup d'œil.
3.class RnnlmGen
Vous pouvez créer une classe générant des phrases à partir de zéro, mais il est plus facile d'ajouter des fonctionnalités à la classe Rnnlm
que vous avez utilisée lors de l'apprentissage du chapitre 6.
En déclarant class RnnlmGen (Rnnlm):
au début du code comme indiqué ci-dessous, toutes les méthodes qui étaient dans class Rnnlm
seront intégrées à class RnnlmGen
. Cela s'appelle ** "héritage" **.
class RnnlmGen(Rnnlm):
def generate(self, start_id, skip_ids=None, sample_size=100):
word_ids = [start_id]
x = start_id #Spécifiez l'identifiant du mot pour la génération de phrases
# word_ids est un échantillon_Continuez jusqu'à ce que la taille soit atteinte
while len(word_ids) < sample_size:
x = np.array(x).reshape(1, 1) #Dans un tableau à deux dimensions(Prise en charge des mini lots)
score = self.predict(x) #Obtenez des résultats de prédiction
p = softmax(score.flatten()) #Normaliser la distribution de probabilité avec Softmax
#10000 de longueur, 1 de taille, choix aléatoire selon la distribution de probabilité de p
sampled = np.random.choice(len(p), size=1, p=p)
# skip_Ignorer les identifiants manquants ou les mots échantillonnés_Pas dans les identifiants
if (skip_ids is None) or (sampled not in skip_ids):
x = sampled
word_ids.append(int(x)) # word_Ajouter aux identifiants
return word_ids
def get_state(self):
return self.lstm_layer.h, self.lstm_layer.c
def set_state(self, state):
self.lstm_layer.set_state(*state)
Voici les méthodes à ajouter à la classe Rnnlm
. Si vous utilisez prédire (x)
pour prédire l'apparence du mot suivant après x et le normaliser avec softmax
, vous obtiendrez une distribution de probabilité p pour le nombre de vocabulaire.
sampled = np.random.choice (len (p), size = 1, p = p)
est un de ** 0 à un entier ** avec le nombre de vocabulaire -1, ce qui signifie que l'échantillonnage aléatoire est effectué selon la distribution de probabilité p. Sera.
Au fait, si vous regardez class Rnnlm
,
class Rnnlm(BaseModel):
def __init__(self, vocab_size=10000, wordvec_size=100, hidden_size=100):
V, D, H = vocab_size, wordvec_size, hidden_size
rn = np.random.randn
#Initialisation du poids
embed_W = (rn(V, D) / 100).astype('f')
lstm_Wx = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_b = np.zeros(4 * H).astype('f')
affine_W = (rn(H, V) / np.sqrt(H)).astype('f')
affine_b = np.zeros(V).astype('f')
#Génération de couches
self.layers = [
TimeEmbedding(embed_W),
TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True),
TimeAffine(affine_W, affine_b)
]
self.loss_layer = TimeSoftmaxWithLoss()
self.lstm_layer = self.layers[1]
#Lister tous les poids et dégradés
self.params, self.grads = [], []
for layer in self.layers:
self.params += layer.params
self.grads += layer.grads
def predict(self, xs):
for layer in self.layers:
xs = layer.forward(xs)
return xs
def forward(self, xs, ts):
score = self.predict(xs)
loss = self.loss_layer.forward(score, ts)
return loss
def backward(self, dout=1):
dout = self.loss_layer.backward(dout)
for layer in reversed(self.layers):
dout = layer.backward(dout)
return dout
def reset_state(self):
self.lstm_layer.reset_state()
Voici le contenu. Cette fois, nous "héritons" de cette classe, donc ces méthodes sont automatiquement intégrées à la classe RnnlmGen
. C'est pratique, n'est-ce pas?
Je ne peux pas vraiment penser que la génération de phrases en anglais fonctionne bien, donc je vais aussi l'essayer en japonais. Cependant, la méthode de prédiction est ** unité de caractère ** au lieu de l'unité de mot.
Cette fois, d'Aozora Bunko, Soseki Natsume's "Je suis un chat" ** fichier texte (avec rubis) ** Téléchargez et utilisez. Après le téléchargement, décompressez-le et enregistrez-le sur ch07 avec le nom ** wagahaiwa_nekodearu.txt **.
import sys
import re
path = './wagahaiwa_nekodearu.txt'
bindata = open(path, "rb")
lines = bindata.readlines()
for line in lines:
text = line.decode('Shift_JIS') # Shift_Lu par JIS
text = re.split(r'\r',text)[0] #Supprimer les sauts de ligne
text = text.replace('|','') #Ruby supprimé
text = re.sub(r'《.+?》','',text) #Ruby supprimé
text = re.sub(r'[#.+?]','',text) #Note d'entrée supprimée
print(text)
file = open('data_neko.txt','a',encoding='utf-8').write(text) # UTF-Convertir en 8
Code de prétraitement. Une fois exécuté, il sera lu au format de fichier texte (Shift-JIS), les sauts de ligne, ruby, note d'entrée, etc. seront supprimés, puis convertis en UTF-8 et enregistrés sous ** data_neko.txt **. Utilisez ensuite l'éditeur pour supprimer manuellement les parties supplémentaires avant et après la phrase.
Ensuite, définissez une fonction load_data ()
qui récupère le corpus, word_to_id, id_to_word de data_neko.txt.
import numpy as np
import io
def load_data():
# file_nom en UTF-Lire en texte au format 8
file_name = './data_neko.txt'
with io.open(file_name, encoding='utf-8') as f:
text = f.read().lower()
# 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])
corpus_test = corpus[300000:] #données de test
corpus = corpus[:300000] #Données d'entraînement
return corpus_test, corpus, word_to_id, id_to_word
Étant donné que le ** corpus ** total créé cette fois-ci est de 318 800 mots, 18 800 mots après 300 000 mots sont désignés comme ** corpus_test **, et les 300 000 mots précédents sont désignés comme ** corpus **.
Maintenant, exécutons le code d'apprentissage utilisé dans ch06 en utilisant le GPU.
import sys
sys.path.append('..')
from common import config
#Lors de l'exécution sur GPU, supprimez le commentaire ci-dessous (cupy requis)
# ==============================================
config.GPU = True
# ==============================================
from common.optimizer import SGD
from common.trainer import RnnlmTrainer
from common.util import eval_perplexity, to_gpu
from dataset import ptb
from ch06.better_rnnlm import BetterRnnlm
#Paramètres des hyper paramètres
batch_size = 20
wordvec_size = 650
hidden_size = 650
time_size = 35
lr = 20.0
max_epoch = 40
max_grad = 0.25
dropout = 0.5
#Lecture des données d'entraînement
corpus_test, corpus, word_to_id, id_to_word = load_data()
corpus_val = corpus_test #Pour plus de simplicité, val et test sont les mêmes
if config.GPU:
corpus = to_gpu(corpus)
corpus_val = to_gpu(corpus_val)
corpus_test = to_gpu(corpus_test)
vocab_size = len(word_to_id)
xs = corpus[:-1]
ts = corpus[1:]
model = BetterRnnlm(vocab_size, wordvec_size, hidden_size, dropout)
optimizer = SGD(lr)
trainer = RnnlmTrainer(model, optimizer)
best_ppl = float('inf')
for epoch in range(max_epoch):
trainer.fit(xs, ts, max_epoch=1, batch_size=batch_size,
time_size=time_size, max_grad=max_grad)
model.reset_state()
ppl = eval_perplexity(model, corpus_val)
print('valid perplexity: ', ppl)
if best_ppl > ppl:
best_ppl = ppl
model.save_params()
else:
lr /= 4.0
optimizer.lr = lr
model.reset_state()
print('-' * 50)
#Évaluation avec données de test
model.reset_state()
ppl_test = eval_perplexity(model, corpus_test)
print('test perplexity: ', ppl_test)
L'apprentissage a été terminé en environ 40 minutes à l'aide de la machine Windows (GTX1060). Une fois terminé, les paramètres de poids entraînés seront enregistrés dans le dossier ch07 sous le nom ** BetterRnnlm.pkl **.
6.class BetterRnnlmGen
Ensuite, définissez class BetterRnnlmGen
. Fondamentalement, il hérite de la class BetterRnnlm
du chapitre 6, mais le ** vocal_size ** de " Je suis un chat " est différent de ** PTB **, donc le paramètre correspondantdef __init___ Écraser en ajoutant ()
(cela s'appelle ** override **).
import sys
sys.path.append('..')
import numpy as np
from common.functions import softmax
from ch06.rnnlm import Rnnlm
from ch06.better_rnnlm import BetterRnnlm
from common.time_layers import * # def __init__Importez les fichiers requis avec
class BetterRnnlmGen(BetterRnnlm):
def __init__(self, vocab_size=3038, wordvec_size=650,
hidden_size=650, dropout_ratio=0.5):
V, D, H = vocab_size, wordvec_size, hidden_size
rn = np.random.randn
embed_W = (rn(V, D) / 100).astype('f')
lstm_Wx1 = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
lstm_Wh1 = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_b1 = np.zeros(4 * H).astype('f')
lstm_Wx2 = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_Wh2 = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_b2 = np.zeros(4 * H).astype('f')
affine_b = np.zeros(V).astype('f')
self.layers = [
TimeEmbedding(embed_W),
TimeDropout(dropout_ratio),
TimeLSTM(lstm_Wx1, lstm_Wh1, lstm_b1, stateful=True),
TimeDropout(dropout_ratio),
TimeLSTM(lstm_Wx2, lstm_Wh2, lstm_b2, stateful=True),
TimeDropout(dropout_ratio),
TimeAffine(embed_W.T, affine_b) # weight tying!!
]
self.loss_layer = TimeSoftmaxWithLoss()
self.lstm_layers = [self.layers[2], self.layers[4]]
self.drop_layers = [self.layers[1], self.layers[3], self.layers[5]]
self.params, self.grads = [], []
for layer in self.layers:
self.params += layer.params
self.grads += layer.grads
def generate(self, start_id, skip_ids=None, sample_size=100):
word_ids = [start_id]
x = start_id
while len(word_ids) < sample_size:
x = np.array(x).reshape(1, 1)
score = self.predict(x).flatten()
p = softmax(score).flatten()
sampled = np.random.choice(len(p), size=1, p=p)
#sampled = np.argmax(p)
if (skip_ids is None) or (sampled not in skip_ids):
x = sampled
word_ids.append(int(x))
return word_ids
def get_state(self):
states = []
for layer in self.lstm_layers:
states.append((layer.h, layer.c))
return states
def set_state(self, states):
for layer, state in zip(self.lstm_layers, states):
layer.set_state(*state)
Enfin, exécutez le code suivant qui génère des phrases.
import sys
sys.path.append('..')
from common.np import *
corpus_test, corpus, word_to_id, id_to_word = load_data()
vocab_size = len(word_to_id)
corpus_size = len(corpus)
model = BetterRnnlmGen()
model.load_params('./BetterRnnlm.pkl')
#Définir les caractères de début et de saut
start_word = 'je'
start_id = word_to_id[start_word]
skip_words = ['〇']
skip_ids = [word_to_id[w] for w in skip_words]
#Génération de phrases(Dès le premier mot)
word_ids = model.generate(start_id, skip_ids)
txt = ''.join([id_to_word[i] for i in word_ids])
print(txt)
#Génération de phrases(De la phrase)
model.reset_state() #Réinitialiser le modèle
start_words = 'Je suis un chat.'
start_ids = [word_to_id[w] for w in start_words.split(' ')] #Convertir en identifiant de mot
#Prédire avant le dernier mot de la phrase(N'utilisez pas les résultats de prédiction)
for x in start_ids[:-1]:
x = np.array(x).reshape(1, 1)
model.predict(x)
word_ids = model.generate(start_ids[-1], skip_ids) #Prédire à partir de l'identifiant du dernier mot de la phrase
word_ids = start_ids[:-1] + word_ids #Concaténer des phrases et des résultats de prédiction
txt = ''.join([id_to_word[i] for i in word_ids]) #Convertir en phrase
print('-' * 50)
print(txt)
Il existe deux modèles de génération de phrases. La première consiste à prédire après un mot et l'autre à prédire après à partir d'une phrase.
Je ne comprends pas du tout le sens dans son ensemble, mais quand je regarde la phrase, je pense que c'est quelque chose que je ne comprends pas.
import sys
sys.path.append('..')
import numpy as np
import matplotlib.pyplot as plt
from dataset import sequence
from common.optimizer import Adam
from common.trainer import Trainer
from common.util import eval_seq2seq
from seq2seq import Seq2seq
from peeky_seq2seq import PeekySeq2seq
#Charger le jeu de données
(x_train, t_train), (x_test, t_test) = sequence.load_data('addition.txt')
char_to_id, id_to_char = sequence.get_vocab()
# Reverse input? =================================================
is_reverse = False # True
if is_reverse:
# [::-1]Trié dans l'ordre inverse, car il est bidimensionnel[:, ::-1]
x_train, x_test = x_train[:, ::-1], x_test[:, ::-1]
# ================================================================
#Paramètres des hyper paramètres
vocab_size = len(char_to_id)
wordvec_size = 16
hidden_size = 128
batch_size = 128
max_epoch = 25
max_grad = 5.0
# Normal or Peeky? ==============================================
model = Seq2seq(vocab_size, wordvec_size, hidden_size)
# model = PeekySeq2seq(vocab_size, wordvec_size, hidden_size)
# ================================================================
optimizer = Adam()
trainer = Trainer(model, optimizer)
acc_list = []
for epoch in range(max_epoch):
trainer.fit(x_train, t_train, max_epoch=1,
batch_size=batch_size, max_grad=max_grad)
correct_num = 0
for i in range(len(x_test)):
question, correct = x_test[[i]], t_test[[i]]
verbose = i < 10
correct_num += eval_seq2seq(model, question, correct,
id_to_char, verbose, is_reverse)
acc = float(correct_num) / len(x_test)
acc_list.append(acc)
print('val acc %.3f%%' % (acc * 100))
#Dessiner un graphique
x = np.arange(len(acc_list))
plt.plot(x, acc_list, marker='o')
plt.xlabel('epochs')
plt.ylabel('accuracy')
plt.ylim(0, 1.0)
plt.show()
C'est un code sobre qui vous rappelle l'ajout. Tout d'abord, jetons un œil à
class Seq2seq
.
9.class Seq2seq
class Seq2seq(BaseModel):
def __init__(self, vocab_size, wordvec_size, hidden_size):
V, D, H = vocab_size, wordvec_size, hidden_size
self.encoder = Encoder(V, D, H)
self.decoder = Decoder(V, D, H)
self.softmax = TimeSoftmaxWithLoss()
self.params = self.encoder.params + self.decoder.params
self.grads = self.encoder.grads + self.decoder.grads
def forward(self, xs, ts):
decoder_xs, decoder_ts = ts[:, :-1], ts[:, 1:]
h = self.encoder.forward(xs)
score = self.decoder.forward(decoder_xs, h)
loss = self.softmax.forward(score, decoder_ts)
return loss
def backward(self, dout=1):
dout = self.softmax.backward(dout)
dh = self.decoder.backward(dout)
dout = self.encoder.backward(dh)
return dout
def generate(self, xs, start_id, sample_size):
h = self.encoder.forward(xs)
sampled = self.decoder.generate(h, start_id, sample_size)
return sampled
Puisque nous combinons uniquement les classes de la classe Encoder et de la classe Decoder, examinons d'abord la classe Encoder.
class Encoder:
def __init__(self, vocab_size, wordvec_size, hidden_size):
V, D, H = vocab_size, wordvec_size, hidden_size
rn = np.random.randn
embed_W = (rn(V, D) / 100).astype('f')
lstm_Wx = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_b = np.zeros(4 * H).astype('f')
self.embed = TimeEmbedding(embed_W)
self.lstm = TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=False)
self.params = self.embed.params + self.lstm.params
self.grads = self.embed.grads + self.lstm.grads
self.hs = None
def forward(self, xs):
xs = self.embed.forward(xs)
hs = self.lstm.forward(xs)
self.hs = hs
return hs[:, -1, :]
def backward(self, dh):
dhs = np.zeros_like(self.hs)
dhs[:, -1, :] = dh
dout = self.lstm.backward(dhs)
dout = self.embed.backward(dout)
return dout
Il s'agit d'un diagramme schématique de ** Encoder **. Les données d'apprentissage sont entrées en séquence et la sortie ** h ** de la dernière étape LSTM est transmise au décodeur.
class Decoder:
def __init__(self, vocab_size, wordvec_size, hidden_size):
V, D, H = vocab_size, wordvec_size, hidden_size
rn = np.random.randn
embed_W = (rn(V, D) / 100).astype('f')
lstm_Wx = (rn(D, 4 * H) / np.sqrt(D)).astype('f')
lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_b = np.zeros(4 * H).astype('f')
affine_W = (rn(H, V) / np.sqrt(H)).astype('f')
affine_b = np.zeros(V).astype('f')
self.embed = TimeEmbedding(embed_W)
self.lstm = TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True)
self.affine = TimeAffine(affine_W, affine_b)
self.params, self.grads = [], []
for layer in (self.embed, self.lstm, self.affine):
self.params += layer.params
self.grads += layer.grads
def forward(self, xs, h):
self.lstm.set_state(h)
out = self.embed.forward(xs)
out = self.lstm.forward(out)
score = self.affine.forward(out)
return score
def backward(self, dscore):
dout = self.affine.backward(dscore)
dout = self.lstm.backward(dout)
dout = self.embed.backward(dout)
dh = self.lstm.dh
return dh
def generate(self, h, start_id, sample_size):
sampled = []
sample_id = start_id
self.lstm.set_state(h)
for _ in range(sample_size):
x = np.array(sample_id).reshape((1, 1))
out = self.embed.forward(x)
out = self.lstm.forward(out)
score = self.affine.forward(out)
sample_id = np.argmax(score.flatten())
sampled.append(int(sample_id))
return sampled
Il s'agit d'un diagramme schématique de ** Decoder **. Le décodeur gère la couche Softmax avec perte après cela différemment entre l'apprentissage et la génération, de sorte que la couche Softmax avec perte est prise en charge par la classe Seq2seq.
L'inversion des données d'entrée accélère le processus d'apprentissage et améliore la précision finale. Exécutez le code du modèle d'addition ci-dessus avec ʻis_reverse = True`.
Juste en inversant, le taux de réponse correct est passé de la plage de 10% à la plage de 50%. Plus le décalage temporel entre chaque élément d'entrée et son élément de sortie est proche, meilleure est la précision. Je vois.
10.PeekyDecoder
La sortie du vecteur h du codeur est une information très importante, mais elle n'est entrée que lors de la première fois du décodeur. Par conséquent, l'idée de saisir à tout moment les informations du vecteur h dans la couche LSTM et la couche Affine est née. Cette méthode s'appelle ** Peeky **. Jetons un coup d'œil à class PeekyDecoder
.
class PeekyDecoder:
def __init__(self, vocab_size, wordvec_size, hidden_size):
V, D, H = vocab_size, wordvec_size, hidden_size
rn = np.random.randn
embed_W = (rn(V, D) / 100).astype('f')
lstm_Wx = (rn(H + D, 4 * H) / np.sqrt(H + D)).astype('f')
lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
lstm_b = np.zeros(4 * H).astype('f')
affine_W = (rn(H + H, V) / np.sqrt(H + H)).astype('f')
affine_b = np.zeros(V).astype('f')
self.embed = TimeEmbedding(embed_W)
self.lstm = TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True)
self.affine = TimeAffine(affine_W, affine_b)
self.params, self.grads = [], []
for layer in (self.embed, self.lstm, self.affine):
self.params += layer.params
self.grads += layer.grads
self.cache = None
def forward(self, xs, h):
N, T = xs.shape
N, H = h.shape
self.lstm.set_state(h)
out = self.embed.forward(xs)
hs = np.repeat(h, T, axis=0).reshape(N, T, H)
out = np.concatenate((hs, out), axis=2)
out = self.lstm.forward(out)
out = np.concatenate((hs, out), axis=2)
score = self.affine.forward(out)
self.cache = H
return score
def backward(self, dscore):
H = self.cache
dout = self.affine.backward(dscore)
dout, dhs0 = dout[:, :, H:], dout[:, :, :H]
dout = self.lstm.backward(dout)
dembed, dhs1 = dout[:, :, H:], dout[:, :, :H]
self.embed.backward(dembed)
dhs = dhs0 + dhs1
dh = self.lstm.dh + np.sum(dhs, axis=1)
return dh
def generate(self, h, start_id, sample_size):
sampled = []
char_id = start_id
self.lstm.set_state(h)
H = h.shape[1]
peeky_h = h.reshape(1, 1, H)
for _ in range(sample_size):
x = np.array([char_id]).reshape((1, 1))
out = self.embed.forward(x)
out = np.concatenate((peeky_h, out), axis=2)
out = self.lstm.forward(out)
out = np.concatenate((peeky_h, out), axis=2)
score = self.affine.forward(out)
char_id = np.argmax(score.flatten())
sampled.append(char_id)
return sampled
Maintenant, dans la génération de modèle du modèle d'addition plus tôt, activez model = PeekySeq2seq (vocab_size, wordvec_size, hidden_size)
et exécutez-le.
C'est un effet dramatique! Le taux de réponse correcte était de 99,1%.
Recommended Posts