Представление модели непрерывного пакета слов с использованием Numpy - PullRequest
0 голосов
/ 04 апреля 2020

Я пытаюсь построить сеть, которая моделирует модель 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')
...