Код для GloVe в глубоком обучении

Код для GloVe. Градиентный спуск в Numpy

Здравствуйте и вновь добро пожаловать на занятия по теме «Обработка естественного языка: глубокое обучение на языке Python, часть 6».

В этой статье мы напишем код для алгоритма метода глобальных векторов, используя Numpy, Theano и для градиентного спуска, а так же сделаем визуализацию аналогий с помощью t-SNE.

Начинаемс импорта библиотек, включая и Theano, хотя пока чтоона нам не нужна. Импортируем также функции get_wikipedia_dataи find_analogies из нашей файла word2vec.

import numpy as np

import json

import os

import theano

import theano.tensor as T

import matplotlib.pyplot as plt

from datetime import datetime

from sklearn.utils import shuffle

from word2vec import get_wikipedia_data, find_analogies

Итак,определим нашу модель глобальных векторов. Вход у нас тот же, что и в word2vec – размерностьвекторных представлений, размер словаря и размур контекста.

class Glove:

    def __init__(self, D, V, context_sz):

        self.D =D

        self.V =V

       self.context_sz = context_sz

Пишем функцию fit.

    def fit(self, sentences, cc_matrix=None, learning_rate=10e-5, reg=0.1,xmax=100, alpha=0.75, epochs=10, gd=False, use_theano=True):

        t0 =datetime.now()

        V =self.V

        D =self.D

        if notos.path.exists(cc_matrix):

            X =np.zeros((V, V))

            N =len(sentences)

           print “number of sentences to process:”, N

            it =0

            forsentence in sentences:

               it += 1

               if it % 10000 == 0:

                   print “processed”, it, “/”, N

               n= len(sentence)

Пишемцикл по каждому токену в предложении. Обратите внимание – i в данном случае не индекс слова! i – индекс индекса слова, то есть позиция слова впредложении. Вот когда делается предложение из i– тогда это индекс слова. Убедитесь, что вы понимаете разницу.

                fori inxrange(n):

                    # i is not the word index!!!

                   # j is not the word index!!!

                   wi = sentence[i]

                   start = max(0, i – self.context_sz)

                   end = min(n, i + self.context_sz)

                   # start and end tokens

                    if i – self.context_sz < 0:

                       points = 1.0 / (i + 1)

                       X[wi,0] += points

                       X[0,wi] += points

                   if i + self.context_sz > n:

                       points = 1.0 / (n – i)

                       X[wi,1] += points

                       X[1,wi] += points

Переходимк левой части контекста. При этом важно не забывать, что контекст имеетсвойство симметрии: если слово i находится в контексте слова j, то и слово j находится вконтексте слова i.

                    # left side

                   for j in xrange(start, i):

                       wj = sentence[j]

                       points = 1.0 / (i – j)

                       X[wi,wj] += points

                       X[wj,wi] += points

Ито же самое с правой частью контекста.

                    # right side

                   for j in xrange(i + 1, end):

                       wj = sentence[j]

                       points = 1.0 / (j – i)

                       X[wi,wj] += points

                       X[wj,wi] += points

Сохраняемнаше X. Название файла – cc_matrix.

            np.save(cc_matrix, X)

        else:

            X =np.load(cc_matrix)

ВычисляемXmax и взвешиваем значения по ужерассматривавшейся формуле.

        print “max value in X:”, X.max()

        fX =np.zeros((V, V))

        fX[X< xmax] = (X[X < xmax] / float(xmax))** alpha

        fX[X>= xmax] = 1

        logX =np.log(X + 1)

        print“time to build co-occurrence matrix:”, (datetime.now() – t0)

Следующее– инициируем весовые коэффициенты.

        # initialize weights

        W = np.random.randn(V, D) / np.sqrt(V + D)

        b =np.zeros(V)

        U =np.random.randn(V, D) / np.sqrt(V + D)

        c =np.zeros(V)

        mu =logX.mean()

Далеемы оставим условный оператор для версии с Theano.Сейчас мы этим заниматься не будем.

        if gd and use_theano:

            pass

Теперьмы можем переходить к основному циклу.

        costs = []

       sentence_indexes = range(len(sentences))

        forepoch in xrange(epochs):

           delta = W.dot(U.T) + b.reshape(V, 1) + c.reshape(1, V) + mu – logX

            cost= ( fX * delta * delta ).sum()

           costs.append(cost)

           print “epoch:”, epoch, “cost:”, cost

Оставимместо для кода в Theano.

            if gd:

               if use_theano:

                   pass

               else:

                   oldW = W.copy()

                    for i in xrange(V):

                       W[i] -= learning_rate*(fX[i,:]*delta[i,:]).dot(U)

                   W-= learning_rate*reg*W

Обновляем b. Возможно, сейчас вам будет не совсем ясно, как мывекторизовали уравнения, но мы делали это ранее множество раз – в «Глубокомобучении, часть 1», логистической регрессии… Так что вы уже должны с этимосвоиться. Но если нет – вернитесь назад и попробуйте сделать всёсамостоятельно.

                    for i in xrange(V):

                       b[i] -=learning_rate*fX[i,:].dot(delta[i,:])

                   b -= learning_rate*reg*b

Обновляем U.

                   for j in xrange(V):

                       U[j] -= learning_rate*(fX[:,j]*delta[:,j]).dot(oldW)

                   U -= learning_rate*reg*U

Теперь обновляем c.

                   for j in xrange(V):

                       c[j] -= learning_rate*fX[:,j].dot(delta[:,j])

                   c-= learning_rate*reg*c

Иоставим место для чередующихся наименьших квадратов.

            else:

               # ALS method

            pass

        self.W =W

        self.U =U

       plt.plot(costs)

       plt.show()

Пришёл черёд функции save.

    def save(self, fn):

        arrays = [self.W, self.U.T]

        np.savez(fn, *arrays)

Следующийэтап – написание функции main.

def main(we_file,w2i_file, n_files=50):

       cc_matrix = “cc_matrix_%s.npy” % n_files

    ifos.path.exists(cc_matrix):

        with open(w2i_file) as f:

           word2idx = json.load(f)

       sentences = []

    else:

           sentences, word2idx = get_wikipedia_data(n_files=n_files, n_vocab=2000)

        with open(w2i_file, ‘w’) as f:

           json.dump(word2idx, f)

    V = len(word2idx)

    model =Glove(80, V, 10)

    model.fit(

        sentences=sentences,

        cc_matrix=cc_matrix,

        learning_rate=3*10e-5,

        reg=0.01,

        epochs=2000,

        gd=True,

        use_theano=False,

    )

   model.save(we_file)

if __name__ == ‘__main__’:

    we =‘glove_model_50.npz’

    w2i =‘glove_word2idx_50.json’

    main(we,w2i, use_brown=True)

    for concatin (True, False):

        print “** concat:”, concat

Проверимуже использовавшиеся словесные аналогии.

        find_analogies(‘king’, ‘man’, ‘woman’, concat, we, w2i)

       find_analogies(‘france’, ‘paris’, ‘london’, concat, we, w2i)

       find_analogies(‘france’, ‘paris’, ‘rome’, concat, we, w2i)

       find_analogies(‘paris’, ‘france’, ‘italy’, concat, we, w2i)

       find_analogies(‘france’, ‘french’, ‘english’, concat, we, w2i)

       find_analogies(‘japan’, ‘japanese’, ‘chinese’, concat, we, w2i)

       find_analogies(‘japan’, ‘japanese’, ‘italian’, concat, we, w2i)

       find_analogies(‘japan’, ‘japanese’, ‘australian’, concat, we, w2i)

        find_analogies(‘december’, ‘november’, ‘june’, concat, we, w2i)

Код для GloVe. Градиентный спуск в Theano

Мы сделаем версию градиентного спуска в Theano. В use_theano ставим флаг True и переходим к месту, где должен быть код Theano. Это место, где идёт if gd and use_theano.

            ifgd and use_theano:

            thW= theano.shared(W)

            thb= theano.shared(b)

            thU= theano.shared(U)

            thc= theano.shared(c)

           thLogX = T.matrix(‘logX’)

            thfX= T.matrix(‘fX’)

            params = [thW, thb, thU, thc]

Обратитевнимание, что мы должны b и c преобразовать в правильную форму. b должно бытьвектором-столбцом, c – вектором-строкой. Иначе ничего работать не будет.

            thDelta= thW.dot(thU.T) + T.reshape(thb, (V, 1)) + T.reshape(thc, (1, V)) + mu – thLogX

            thCost = ( thfX * thDelta * thDelta ).sum()

           grads = T.grad(thCost, params)

           updates = [(p, p – learning_rate*g) for p, g in zip(params, grads)]

           train_op = theano.function(

                inputs=[thfX, thLogX],

                updates=updates,

            )

Ипереходим к месту выполнения градиентного спуска. Тут всё просто, ведь train_op мы ужеопределили. Мы должны её только вызвать.

            ifgd:

                ifuse_theano:

                    train_op(fX, logX)

                   W = thW.get_value()

                   b = thb.get_value()

                   U = thU.get_value()

                   c = thc.get_value()

Всё сделано. Запустим наш файл.

Код для GloVe. Чередующиеся наименьшие квадраты

Наэтой лекции мы продолжим наш пример с написанием кода для GloVe и реализуем метод чередующихся наименьшихквадратов.

Итак,переходим к месту, оставленному для чередующихся наименьших квадратов.

Вначале обновляем w.

               # ALS method

               for i in xrange(V):

                   matrix = reg*np.eye(D) + (fX[i,:]*U.T).dot(U)

                   vector = (fX[i,:]*(logX[i,:] – b[i] – c – mu)).dot(U)

                   W[i] = np.linalg.solve(matrix, vector)

Незабывайте, что векторные и матричные операции в Numpyнамного более эффективны, поэтому не стоит писать прямое решение. Вы можетесделать это вначале, чтобы убедиться в правильности своего решения, но вообщеговоря нужно переводить уравнения в векторную форму, поскольку так намногобыстрее. Если вы хотите посмотреть первоначальное решение, без перевода ввекторную форму, то зайдите в репозитарий Github.

                for i in xrange(V):

                   denominator = fX[i,:].sum()

                   numerator = fX[i,:].dot(logX[i,:] – W[i].dot(U.T) – c – mu)

                   b[i] = numerator / denominator / (1 + reg)

Итак,мы обновили wи b.Перейдём теперь к обновлению u и c.

                for j in xrange(V):

                   matrix = reg*np.eye(D) + (fX[:,j]*W.T).dot(W)

                   vector = (fX[:,j]*(logX[:,j] – b – c[j] – mu)).dot(W)

                   U[j] = np.linalg.solve(matrix, vector)

Ипоследнее обновление – для c.

                for j in xrange(V):

                   denominator = fX[:,j].sum()

                   numerator = fX[:,j].dot(logX[:,j] – W.dot(U[j]) – b  – mu)

                   c[j] = numerator / denominator / (1 + reg)

Ивнизу в функции main.

    model.fit(sentences,cc_matrix=cc_matrix, epochs=20) # ALS

Запустим наш код.

Визуализация аналогий по странам с помощью t-SNE

Сейчас мы напишем код для наглядной демонстрации векторных представлений слов, полученных с помощью метода глобальных векторов, в частности, посмотрим на страны и национальности, чтобы узнать, получилось ли найти какие-то интересные закономерности.

Итак,начинаем с импорта обычных библиотек – Json,Numpy, Matplotlib и SkiKit Learn.

import json

import numpy as np

import matplotlib.pyplot as plt

from sklearn.manifold import TSNE

Определяемнашу функцию main и указываем конкретные слова, которые хотим увидетьна диаграмме: «Япония», «японский», «Англия», «английский», «Австралия»,«австралийский» и другие. В качестве упражнения можете попробовать что-то своё,чтобы найти совсем другие аналогии.

def main(we_file=’glove_model_50.npz’,w2i_file=’glove_word2idx_50.json’):

    words =[‘japan’, ‘japanese’, ‘england’, ‘english’, ‘australia’, ‘australian’, ‘china’,‘chinese’, ‘italy’, ‘italian’, ‘french’, ‘france’, ‘spain’, ‘spanish’]

    with open(w2i_file) as f:

        word2idx= json.load(f)

    npz =np.load(we_file)

    W =npz[‘arr_0’]

    V =npz[‘arr_1’]

    We = (W +V.T) / 2

    idx = [word2idx[w] for w in words]

    tsne =TSNE()

    Z =tsne.fit_transform(We)

    Z = Z[idx]

   plt.scatter(Z[:,0], Z[:,1])

    for i in xrange(len(words)):

       plt.annotate(s=words[i], xy=(Z[i,0], Z[i,1]))

    plt.show()

if __name__ == ‘__main__’:

main()

Запустимпрограмму и посмотрим, что получится.

Итак, любопытно, что все национальности расположены в одной области, а названия стран – в другой. В предыдущем случае использования t-SNE мы видели, что некоторые слова располагались неуместно. В данном же примере мы видим, что похожие слова находятся в относительной близости друг от друга, даже в случае рассмотрения похожих, но всё же различных понятий или, другими словами, аналогий.

Проблема гиперпараметров

В завершении нашей темы, я хочу прямо предложить вам выполнить ещё одно упражнение. Я часто косвенно предлагаю упражнения, но в данном случае очень важно, чтобы вы сделали его самостоятельно.

Каквы могли заметить, наши словесные аналогии были отнюдь не идеальными. Вчастности, при проведении аналогий со странами и городами мы получили Берлин,когда ожидался Рим. Я предлагаю провести больше словесных аналогий для проверкиэффективности модели. Мы уже перепробовали страны и города, страны инациональности, месяцы года. Но есть и другие варианты. Можно попробоватьпрофессии. К примеру, программисты создают программы, а художники – картины.Это тоже словесные аналогии.

Естьряд гиперпараметров, с которыми можно поиграть для получения лучшегорезультата. Есть привычные нам коэффициент обучения, импульс и регуляризация.От импульса вряд ли будет польза, поскольку в моделях вроде word2vec на каждом этапеобновляются различные векторы, так что смысла в его изменении не будет. Чтокасается регуляризации, что иногда длину всех слов-векторов устанавливаютравной 1.

Крометого, мы можем захотеть, чтобы наша модель узнала как можно больше о том, какиспользуются слова. В этом случае нам нужно использовать как можно большеучебных файлов. Но не забывайте: чем больше имеющихся данных, тем дольше будетидти обучение. Исключением тут является метод глобальных векторов, при которомобучение всегда происходит на матрице размерностью VxV. Много времени тут занимает толькопредварительная работа по созданию целевой матрицы.

Регулируютсятакже размерность векторных представлений слов и размер словаря. Можнопоэкспериментировать с ними. Но если размерность представлений чересчур велика,то пространство поиска будет слишком большим, если же чересчур низка, то модельне сможет правильно разместить все векторы. Если чересчур большим будет размерсловаря, то может не хватить данных, но при чересчур малом словаре вряд линайдутся интересные аналогии. Попробуйте самостоятельно найти компромиссныезначения величин и поделитесь своими результатами с коллегами-слушателями.

Андрей Никитенко
Андрей Никитенко
Задать вопрос эксперту
Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Share via
Copy link