Я пытаюсь построить сеть, которая моделирует модель Continuous Bag of Words в NumPy, и я пытаюсь выяснить, где я ошибся здесь. Моя сеть, кажется, не учится вообще, и предсказывает каждый набор к тому же самому слову. Очевидно, есть кое-что, что я принципиально сделал неправильно, но я не могу точно определить, где именно.
Поэтому мой подход следующий:
Разбить корпус на два вектора (contexts
и targets
). Каждый набор элементов в контекстном окне в векторе contexts
сопоставляется с записью в targets
. Таковы мои X_train
и Y_train
. Например, с размером контекста 2
слова ["привет", "как", "ты", "делаешь"] могут сопоставляться с целевым словом "являются", поскольку это центральное слово корпуса ["Привет, как твои дела"]. Центральное слово является целью, а слова +-CONTEXT_SIZE
принадлежат contexts
.
Получите индексирование токенов из корпуса и передайте его в слой внедрения, который имеет форму (vocab_size, embedding_size)
. Слой встраивания - это справочная таблица, которая отображается из измерений vocab_size
в embedding_size
, поэтому я буду смотреть на вектор встраивания, представляющий среднее контекстных слов, то есть word_vec
. Итак, этот вектор имеет форму (1, embedding_size)
(вектор столбца).
Теперь я передаю word_vec
(средний вектор вложенного слова) в скрытый слой (формы embedding_size, vocab_size)
. У этого есть функция активации tanh
со смещением. Поэтому мне также нужно нормализовать мой вектор после умножения его на W
. Выходной вектор из этого слоя будет иметь форму (1, vocab_size)
, поэтому в основном, я могу просто передать это своей функции softmax.
После получения индекса слова с помощью softmax я нахожу максимальную вероятность и прогнозирую результат. Должно быть все в порядке, верно?
Кажется, что мои градиенты имеют тенденцию взрываться, но отсечение их, кажется, ничего не исправляет, так как они застрянут на пороговых значениях. Кроме того, обновления веса кажутся слишком маленькими , так что, вероятно, во время прямого прохода произошла ошибка. Я опубликую полный код ниже. Буду признателен, если кто-нибудь укажет мне правильные шаги. Была ли это какая-то ошибка во время инициализации, или я go wro Где-нибудь еще?
РЕДАКТИРОВАТЬ : записная книжка Colab здесь: https://colab.research.google.com/drive/1CCyr_Xi-hQXchvatQFnKeXgC1hUFXHSJ
import numpy as np
import re
from nltk.corpus import brown
import matplotlib.pyplot as plt
class CBOW:
def __init__(self, contexts, targets, vocab_size, embedding_size, learning_rate, context_size=3):
# Initialize Stuff
self.embedding_matrix = np.random.randn(vocab_size, embedding_size) * 0.01
self.num_classes = vocab_size
self.W = np.random.randn(embedding_size, self.num_classes) * 0.01
self.bias = np.zeros((1, self.W.shape[1]))
self.word_vec = None
self.gradients = dict()
self.cache_contexts = None # For backprop
self.cache_Z = None # For backprop
self.learning_rate = learning_rate
self.contexts = contexts
self.targets = targets
self.context_width = 2*context_size
def Embedding(self, contexts, train):
# Op.shape = 1 * embedding_size (Column Vector)
# Take the average vector from the context window
word_vec = np.mean(self.embedding_matrix[contexts, :], axis=1)
#assert word_vec.shape == (self.context_width, self.embedding_matrix.shape[1])
assert word_vec.shape == (1, self.embedding_matrix.shape[1])
self.word_vec = word_vec
def tanh(self, inp):
return np.tanh(inp)
def tanh_delta(self, inp):
return (1 - inp*inp)
def Dense(self, word_vec):
# Pass the word_vec thru the Dense Layer
op = np.dot(word_vec, self.W) + self.bias
# Normalize before passing to tanh
op = op / (np.sum(1e-2 + np.max(op), keepdims=True))
op = self.tanh(op)
assert op.shape == (1, self.W.shape[1])
return op
def softmax(self, inp):
assert inp.shape == (1, self.num_classes)
max_val = np.max(inp, axis=1)
softmax_out = np.exp(inp - max_val) / (np.sum(np.exp(inp - max_val), keepdims=True) + 1e-3)
return softmax_out
def forward(self, contexts, train):
self.Embedding(contexts, train)
Z = self.Dense(self.word_vec)
self.cache_Z = Z
softmax_out = softmax(Z)
self.cache_contexts = contexts
return softmax_out
def cross_entropy(self, softmax_out, Y):
target = np.zeros(softmax_out.shape)
target[:, np.squeeze(Y)] = 1
loss = -(target * np.log(softmax_out + 1e-3))
return np.max(loss)
def backward(self, Y, softmax_out):
# Perform backprop
# Z = tanh(O)
# O = W*word_vec + bias
# softmax_out = softmax(Z)
# dL/d(Z) = (softmax_out - Y)
Z = self.cache_Z
target = np.zeros(softmax_out.shape)
target[:, np.squeeze(Y)] = 1
dL_dZ = softmax_out - target
assert dL_dZ.shape == (1, self.num_classes)
self.gradients['dL_dZ'] = dL_dZ
# dZ_dO = (1 - tanh^2(O)) = (1 - z*z)
dZ_dO = self.tanh_delta(Z)
assert dZ_dO.shape == (1, self.W.shape[1]) # (1, num_classes)
# dL/dO = dL_dZ * dZ/dO
dL_dO = dL_dZ * dZ_dO
assert dL_dO.shape == (1, self.W.shape[1])
self.gradients['dL_dO'] = dL_dO
# dL/d(W) = dL/d(O) * d(O)/d(W)
# d(O)/dW = word_vec
dO_dW = self.word_vec
dL_dW = np.dot(dO_dW.T, dL_dO)
assert dL_dW.shape == self.W.shape # (embedding_size, num_classes)
self.gradients['dL_dW'] = dL_dW
# dL/d(word_vec) = dL/dO * dO/d(word_vec) = dL/dO * W
dL_dword_vec = np.dot(dL_dO, self.W.T)
assert dL_dword_vec.shape == self.word_vec.shape # (1, embedding_size)
self.gradients['dL_dword_vec'] = dL_dword_vec
# dL/d(bias) = dL/dO * dO/d(bias) = dL/dO
dL_dbias = dL_dO
assert dL_dbias.shape == self.bias.shape
self.gradients['dL_dbias'] = dL_dbias
# Clip all gradients
for grad in self.gradients:
self.gradients[grad] = np.clip(self.gradients[grad], -500, 500)
#print(self.gradients)
def update(self):
contexts = self.cache_contexts
dL_dword_vec = self.gradients['dL_dword_vec']
self.embedding_matrix[contexts] -= self.learning_rate * dL_dword_vec
self.W -= self.learning_rate * self.gradients['dL_dW']
self.bias -= self.learning_rate * self.gradients['dL_dbias']
def train(self, epochs):
losses = []
X = self.contexts
Y = self.targets
vocab_size = self.num_classes
context_width = Y.shape[1]
for epoch in range(epochs):
epoch_loss = 0
factor = (2 * CONTEXT_SIZE)
inds = list(range(0, context_width))
np.random.shuffle(inds)
print(f"Item #{inds[0]}")
for i in inds:
X_item = X[:, i*factor:(i+1)* factor]
Y_item = Y[:, i:i+1]
softmax_out = self.forward(X_item, train=True)
self.backward(Y_item, softmax_out)
self.update()
loss = self.cross_entropy(softmax_out, Y_item)
epoch_loss += np.squeeze(loss)
losses.append(epoch_loss)
if epoch:
print(f"Loss after epoch #{epoch}: {epoch_loss}")
plt.plot(np.arange(epochs), losses)
plt.xlabel('# of epochs')
plt.ylabel('cost')