Je lis un chef-d'œuvre, ** "Deep Learning from Zero 2" **. Cette fois, c'est un mémo du chapitre 4. Pour exécuter le code, téléchargez le code complet depuis Github et utilisez le notebook jupyter dans ch04.
Le thème du chapitre 4 est d'accélérer le modèle CBOW Word2vec implémenté dans le chapitre 3 et d'en faire un modèle pratique. Exécutez ch04 / train.py et regardez le contenu dans l'ordre.
L'ensemble de données utilise Penn Tree Bank, le nombre de vocabulaire est de 10 000 et la taille du corpus du train. Est d'environ 900 000 mots.
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.np import *
import pickle
from common.trainer import Trainer
from common.optimizer import Adam
from cbow import CBOW
from skip_gram import SkipGram
from common.util import create_contexts_target, to_cpu, to_gpu
from dataset import ptb
#Paramètres des hyper paramètres
window_size = 5
hidden_size = 100
batch_size = 100
max_epoch = 10
#Lire les données
corpus, word_to_id, id_to_word = ptb.load_data('train')
vocab_size = len(word_to_id)
#Obtenez le contexte et la cible
contexts, target = create_contexts_target(corpus, window_size)
if config.GPU:
contexts, target = to_gpu(contexts), to_gpu(target)
#Construction de réseau
model = CBOW(vocab_size, hidden_size, window_size, corpus)
#Apprentissage, affichage du graphique de transition de perte
optimizer = Adam()
trainer = Trainer(model, optimizer)
trainer.fit(contexts, target, max_epoch, batch_size)
trainer.plot()
#Enregistrez les données dont vous avez besoin pour une utilisation ultérieure
word_vecs = model.word_vecs
if config.GPU:
word_vecs = to_cpu(word_vecs)
params = {}
params['word_vecs'] = word_vecs.astype(np.float16)
params['word_to_id'] = word_to_id
params['id_to_word'] = id_to_word
pkl_file = 'cbow_params.pkl' # or 'skipgram_params.pkl'
with open(pkl_file, 'wb') as f:
pickle.dump(params, f, -1)
La perte semble avoir diminué régulièrement. Ensuite, cela devient un point. Jetons un coup d'œil à class CBOW
dans cbow.py
dans la partie construction du réseau.
# --------------- from cbow.py ---------------
class CBOW:
def __init__(self, vocab_size, hidden_size, window_size, corpus):
V, H = vocab_size, hidden_size
#Initialisation du poids
W_in = 0.01 * np.random.randn(V, H).astype('f')
W_out = 0.01 * np.random.randn(V, H).astype('f')
#Génération de couches
self.in_layers = []
for i in range(2 * window_size):
layer = Embedding(W_in) #Utiliser le calque d'incorporation
self.in_layers.append(layer)
self.ns_loss = NegativeSamplingLoss(W_out, corpus, power=0.75, sample_size=5)
#Lister tous les poids et dégradés
layers = self.in_layers + [self.ns_loss]
self.params, self.grads = [], []
for layer in layers:
self.params += layer.params
self.grads += layer.grads
#Définir une représentation distribuée des mots dans les variables membres
self.word_vecs = W_in
L'un des points d'accélération est l'adoption de la ** couche d'intégration **. Jetez un œil à common / layer.py.
# --------------- from common/layers.py --------------
class Embedding:
def __init__(self, W):
self.params = [W]
self.grads = [np.zeros_like(W)]
self.idx = None
def forward(self, idx):
W, = self.params
self.idx = idx
out = W[idx] #Sortie de la ligne spécifiée par idx
return out
def backward(self, dout):
dW, = self.grads
dW[...] = 0
if GPU:
np.scatter_add(dW, self.idx, dout)
else:
np.add.at(dW, self.idx, dout) #Ajouter des données à la ligne spécifiée par idx
return None
Dans le chapitre 3, la ** couche MatMul ** a été utilisée pour trouver le produit interne du vecteur et de la matrice de poids, mais quand on y réfléchit, c'est le produit interne du vecteur one-hot et de la matrice de poids, donc ** la matrice de poids $ W_ {in} Tout ce que vous avez à faire est de spécifier la ligne $ **. Il s'agit de la ** couche incorporée **.
De cette façon, la rétropropagation n'a besoin que de mettre à jour la ligne correspondante avec les données précédemment transmises. Cependant, dans l'apprentissage par mini-lots, il est possible que plusieurs données reviennent à la même ligne et se chevauchent, donc ** les données sont ajoutées ** au lieu d'être remplacées.
4.Negative Sampling Le deuxième point d'accélération est ** l'échantillonnage négatif **. Comme dans le chapitre 3, il n'est pas réaliste de classer par Softmax à partir de la sortie du nombre de vocabulaire. Alors que devons-nous faire. La réponse est ** de résoudre le problème de classification à valeurs multiples en l'approximant au problème de classification binaire **.
Jetez un œil à class NegativeSamplingLoss
dans negative_sampling_layer.py
.
# ------------- form negative_sampling_layer.py --------------
class NegativeSamplingLoss:
def __init__(self, W, corpus, power=0.75, sample_size=5):
self.sample_size = sample_size
self.sampler = UnigramSampler(corpus, power, sample_size)
self.loss_layers = [SigmoidWithLoss() for _ in range(sample_size + 1)]
self.embed_dot_layers = [EmbeddingDot(W) for _ in range(sample_size + 1)]
self.params, self.grads = [], []
for layer in self.embed_dot_layers:
self.params += layer.params
self.grads += layer.grads
def forward(self, h, target):
batch_size = target.shape[0]
negative_sample = self.sampler.get_negative_sample(target)
#Exemple positif en avant
score = self.embed_dot_layers[0].forward(h, target)
correct_label = np.ones(batch_size, dtype=np.int32)
loss = self.loss_layers[0].forward(score, correct_label)
#Négatif en avant
negative_label = np.zeros(batch_size, dtype=np.int32)
for i in range(self.sample_size):
negative_target = negative_sample[:, i]
score = self.embed_dot_layers[1 + i].forward(h, negative_target)
loss += self.loss_layers[1 + i].forward(score, negative_label)
return loss
def backward(self, dout=1):
dh = 0
for l0, l1 in zip(self.loss_layers, self.embed_dot_layers):
dscore = l0.backward(dout)
dh += l1.backward(dscore)
return dh
Pour rapprocher la classification à valeurs multiples de la classification binaire, commencez par établir la probabilité que dire (1) est correct pour la réponse du mot entre vous (0) et au revoir (2) autant que possible (correct). Exemple). Mais ce n'est pas assez.
Par conséquent, j'ajoute que la probabilité qu'un bonjour (5) ou I (4) correctement sélectionné soit incorrect est aussi grande que possible (exemple négatif).
Cette technique est appelée ** Échantillonnage négatif **. Le nombre d'exemples négatifs parmi lesquels choisir est sample_size = 5 dans le code.
Ici, ʻEmbedding_dot_layersapparaît, alors jetons un œil à cela également. De même, il se trouve dans
negatif_sampling_layer.py`.
# ------------- form negative_sampling_layer.py --------------
class EmbeddingDot:
def __init__(self, W):
self.embed = Embedding(W)
self.params = self.embed.params
self.grads = self.embed.grads
self.cache = None
def forward(self, h, idx):
target_W = self.embed.forward(idx)
out = np.sum(target_W * h, axis=1)
self.cache = (h, target_W)
return out
def backward(self, dout):
h, target_W = self.cache
dout = dout.reshape(dout.shape[0], 1)
dtarget_W = dout * h
self.embed.backward(dtarget_W)
dh = dout * target_W
return dh
Afin de prendre en charge le mini-lot, la somme de target_w * h est prise à la fin afin qu'elle puisse être calculée même s'il y a plusieurs idx et h.
Tout d'abord, nous avons déplacé ch04 / train.py, donc les paramètres appris sont stockés dans cbow_params.pkl
. Utilisez ceci pour vérifier ʻeval.py` pour voir si la représentation distribuée des mots est bonne.
import sys
sys.path.append('..')
from common.util import most_similar, analogy
import pickle
pkl_file = 'cbow_params.pkl' #Spécification du nom de fichier
#Lecture de chaque paramètre
with open(pkl_file, 'rb') as f:
params = pickle.load(f)
word_vecs = params['word_vecs']
word_to_id = params['word_to_id']
id_to_word = params['id_to_word']
# most similar task
querys = ['you']
for query in querys:
most_similar(query, word_to_id, id_to_word, word_vecs, top=5)
Commencez par vérifier la similarité des mots en utilisant la méthode most_similar
(common / util.py). La chose la plus proche de vous est nous, et moi, eux, votre, suivis de synonymes personnels. Ceci est le résultat du calcul de la similitude de chaque mot avec la similitude cosinus suivante.
# analogy task
analogy('king', 'man', 'queen', word_to_id, id_to_word, word_vecs)
Voyons maintenant le fameux problème ** king --man + woman = queen ** en utilisant la méthode ʻalalogy` (common / util.py). C'est vrai, non?
Cela résout la tâche de trouver le ** mot x ** de sorte que le vecteur ** "roi → x" ** soit aussi proche que possible du ** vecteur "homme → femme" **.
Recommended Posts