Использование нейронных сетей для решения задач NLP

Понятие разметки по частям речи

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

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

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

Итак, как же мы будем решать эту задачу? Для начала рассмотрим наши данные. Данные вы можете получить по адресу http://www.cnts.ua.ac.be/conll2000/chunking/. Вы должны скачать файлы train.txt и test.txt и перенести в папку chunking. На слайде показан пример наших данных.

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

Когдамы сталкиваемся с задачей машинного обучения, полезно отталкиваться от базовогоуровня. Какую мы знаем простейшую модель, пригодную для классификации?Логистическую регрессию! Вы можете убедиться, как легко логистическая регрессияможет быть применена для нашей задачи: необходимо просто использовать прямоекодирование (one-hot encoding) для слов и меток, после чегоиспользовать функцию софтмакс. При этом можно добиться очень хорошихрезультатов и точности более 90%. Это значит, что более чем в 90% случаев словоимеет ту же метку, что и указанная моделью. Редко когда одно и то же словоможет быть использовано в двух разных смыслах, например «Я собираюсь печьпироги» и «Печь сильно дымит».

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

Увас может возникнуть вопрос: можем ли мы воспользоваться последовательностью,то есть контекстом, для создания лучшего прогноза модели? Замечательнейшиминструментом для этого могут служить рекуррентные нейронные сети, которые мырассматривали в «Глубоком обучении, часть 5». Заложив базовый уровень, мыпосмотрим, как рекуррентные нейронные сети справляются с этой задачей.

Нои это ещё не все. В курсе, предваряющем курс по рекуррентным нейронным сетям,вы изучили скрытые марковские модели. В частности, слова можно рассматриватькак наблюдаемую последовательность, а метки – как последовательность скрытыхсостояний, а потому скрытые марковские модели кажутся отличным решением даннойзадачи. Впрочем, есть одна маленькая загвоздка. Если вы изучали курс по скрытыммарковским моделям, то помните, что для обучения скрытой марковской моделииспользуется так называемый алгоритм максимизации ожиданий. Как вы думаете,здесь он нужен? Ответ: нет, конечно же нет! В данном случае мы находимся в тойинтересной ситуации, когда на самом деле знаем скрытые состояния обучающихданных. Это один из тех случаев, когда скрытые состояния являются не какими-тоабстрактными гиперпараметрами, а вполне реальной, осязаемой вещью.

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

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

Разметка по частям речи. Базовая модель

В этом первом разделе мы напишем код для базового решения задачи разметки по частям речи.

Итак,начинаем с импорта библиотек. Мы создадим собственную логистическую регрессию спомощью Theano, поскольку использование регрессии в версии Sciki-Learn будет плохимрешением: потребуется слишком много данных и, как следствие, будет происходитьочень медленно. Поэтому мы воспользуемся Theanoдля реализации стохастического градиентного спуска. Мы также испытаем алгоритмдеревьев решений, просто для проверки – удастся ли получить похожий результат.

import numpy as np

import theano

import theano.tensor as T

import matplotlib.pyplot as plt

from sklearn.utils import shuffle

from sklearn.metrics import f1_score

from sklearn.tree import DecisionTreeClassifier

Итак, определим класс LogisticRegression для логистической регрессии.

class LogisticRegression:

    def __init__(self):

        pass

    def fit(self, X, Y, V=None, K=None,lr=10e-1, mu=0.99, batch_sz=100, epochs=6):

        if V isNone:

            V =len(set(X))

        if K isNone:

            K =len(set(Y))

        N = len(X)

Инициируемвесовые коэффициенты.

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

        b =np.zeros(K)

        self.W =theano.shared(W)

        self.b =theano.shared(b)

       self.params = [self.W, self.b]

        thX =T.ivector(‘X’)

        thY =T.ivector(‘Y’)

Вычислям p(y|x).

        py_x =T.nnet.softmax(self.W[thX] + self.b)

       prediction = T.argmax(py_x, axis=1)

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

        cost = -T.mean(T.log(py_x[T.arange(thY.shape[0]), thY]))

        grads =T.grad(cost, self.params)

        dparams= [theano.shared(p.get_value()*0) for p in self.params]

       self.cost_predict_op = theano.function(

            inputs=[thX, thY],

            outputs=[cost, prediction],

            allow_input_downcast=True,

        )

        updates= [

            (p,p + mu*dp – lr*g) for p, dp, g in zip(self.params, dparams, grads)

        ] + [

            (dp,mu*dp – lr*g) for dp, g in zip(dparams, grads)

        ]

        train_op= theano.function(

            inputs=[thX, thY],

            outputs=[cost, prediction],

            updates=updates,

            allow_input_downcast=True

        )

        costs =[]

       n_batches = N / batch_sz

        for i inxrange(epochs):

            X, Y= shuffle(X, Y)

           print(“epoch:”, i)

            forj in xrange(n_batches):

               Xbatch = X[j*batch_sz:(j*batch_sz + batch_sz)]

               Ybatch = Y[j*batch_sz:(j*batch_sz + batch_sz)]

               c, p = train_op(Xbatch, Ybatch)

               costs.append(c)

               if j % 200 == 0:

                   print “i:”, i, “j:”, j, “n_batches:”,n_batches, “cost:”, c, “error:”, np.mean(p != Ybatch)

       plt.plot(costs)

       plt.show()

    def score(self, X, Y):

        _, p =self.cost_predict_op(X, Y)

        returnnp.mean(p == Y)

    def f1_score(self, X, Y):

        _, p =self.cost_predict_op(X, Y)

        returnf1_score(Y, p, average=None).mean()

Переходимк функции get_data. В отличие отрекуррентных нейронных сетей, где каждое предложение рассматривалось какотдельная последовательность, здесь мы последовательности разбивать не будем.

def get_data(split_sequences=False):

    word2idx ={}

    tag2idx = {}

    word_idx = 0

    tag_idx = 0

    Xtrain = []

    Ytrain = []

    currentX =[]

    currentY =[]

    for line in open(‘chunking/train.txt’):

        line = line.rstrip()

        if line:

            r =line.split()

           word, tag, _ = r

            ifword not in word2idx:

               word2idx[word] = word_idx

               word_idx += 1

           currentX.append(word2idx[word])

            iftag not in tag2idx:

               tag2idx[tag] = tag_idx

               tag_idx += 1

           currentY.append(tag2idx[tag])

        elifsplit_sequences:

           Xtrain.append(currentX)

           Ytrain.append(currentY)

           currentX = []

           currentY = []

    if notsplit_sequences:

        Xtrain =currentX

        Ytrain =currentY

    Xtest = []

    Ytest = []

    currentX =[]

    currentY =[]

    for line in open(‘chunking/test.txt’):

        line =line.rstrip()

        if line:

            r =line.split()

           word, tag, _ = r

            ifword in word2idx:

               currentX.append(word2idx[word])

           else:

                currentX.append(word_idx)

           currentY.append(tag2idx[tag])

        elifsplit_sequences:

           Xtest.append(currentX)

           Ytest.append(currentY)

           currentX = []

           currentY = []

    if notsplit_sequences:

        Xtest =currentX

        Ytest =currentY

    returnXtrain, Ytrain, Xtest, Ytest, word2idx

Следующее– определение функции main.

defmain():

    Xtrain,Ytrain, Xtest, Ytest, word2idx = get_data()

ПреобразуемXtrain и Ytrain в массивы Numpy, поскольку с ними удобнее работать.

    Xtrain =np.array(Xtrain)

    Ytrain =np.array(Ytrain)

    N =len(Xtrain)

    V =len(word2idx) + 1

Теперьсоздаём классификатор деревьев решений.

    dt =DecisionTreeClassifier()

   dt.fit(Xtrain.reshape(N, 1), Ytrain)

    print “dttrain score:”, dt.score(Xtrain.reshape(N, 1), Ytrain)

    p =dt.predict(Xtrain.reshape(N, 1))

    print “dttrain f1:”, f1_score(Ytrain, p, average=None).mean()

Имодель логистической регрессии.

    model = LogisticRegression()

    model.fit(Xtrain,Ytrain, V=V)

    print“training complete”

    print “lrtrain score:”, model.score(Xtrain, Ytrain)

    print “lrtrain f1:”, model.f1_score(Xtrain, Ytrain)

    Ntest =len(Xtest)

    Xtest =np.array(Xtest)

    Ytest =np.array(Ytest)

    print “dttest score:”, dt.score(Xtest.reshape(Ntest, 1), Ytest)

    p =dt.predict(Xtest.reshape(Ntest, 1))

    print “dttest f1:”, f1_score(Ytest, p, average=None).mean()

    print “lrtest score:”, model.score(Xtest, Ytest)

    print “lrtest f1:”, model.f1_score(Xtest, Ytest)

if __name__ == ‘__main__’:

main()

Разметка по частям речи. Рекуррентная нейронная сеть

Итак продолжим решение задач, начинаем с импорта библиотек. Поскольку мы уже написали код класса управляемого рекуррентного нейрона в курсе по рекуррентным нейронным сетям, нам нет нужды писать его ещё раз. Мы просто импортируем его из папки с курсом по рекуррентным сетям. То же самое – с функцией get_data. Мы её описали на прошлом занятии. Кроме того, из файла util.py возьмём функцию init_weight.

import numpy as np

import matplotlib.pyplot as plt

import theano

import theano.tensor as T

import os

import sys

sys.path.append(os.path.abspath(‘..’))

from rnn_class.gru import GRU

from pos_baseline import get_data

from sklearn.utils import shuffle

from util import init_weight

from datetime import datetime

from sklearn.metrics import f1_score

Итак,определим класс для рекуррентной нейронной сети.

class RNN:

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

       self.hidden_layer_sizes = hidden_layer_sizes

        self.D =D

        self.V =V

Коэффициентобучения установим по умолчанию равным 10-5, функция активации – relu; в сети мы используем управляемый рекуррентныйнейрон, но если хотите – попробуйте сеть долгой краткосрочной памяти.

    def fit(self, X, Y, learning_rate=10e-5, mu=0.99, epochs=30, show_fig=True,activation=T.nnet.relu, RecurrentUnit=GRU, normalize=False):

        D =self.D

        V =self.V

        N =len(X)

        We =init_weight(V, D)

       self.hidden_layers = []

        Mi = D

        for Moin self.hidden_layer_sizes:

            ru =RecurrentUnit(Mi, Mo, activation)

           self.hidden_layers.append(ru)

            Mi =Mo

        Wo =init_weight(Mi, self.K)

        bo =np.zeros(self.K)

        self.We= theano.shared(We)

        self.Wo= theano.shared(Wo)

        self.bo= theano.shared(bo)

       self.params = [self.Wo, self.bo]

        for ruin self.hidden_layers:

           self.params += ru.params

        thX =T.ivector(‘X’)

        thY =T.ivector(‘Y’)

        Z =self.We[thX]

        for ruin self.hidden_layers:

            Z =ru.output(Z)

        py_x =T.nnet.softmax(Z.dot(self.Wo) + self.bo)

       prediction = T.argmax(py_x, axis=1)

        cost =-T.mean(T.log(py_x[T.arange(thY.shape[0]), thY]))

        grads =T.grad(cost, self.params)

        dparams= [theano.shared(p.get_value()*0) for p in self.params]

        dWe =theano.shared(self.We.get_value()*0)

        gWe =T.grad(cost, self.We)

       dWe_update = mu*dWe – learning_rate*gWe

       We_update = self.We + dWe_update

        ifnormalize:

           We_update /= We_update.norm(2)

        updates= [

            (p,p + mu*dp – learning_rate*g) for p, dp, g in zip(self.params, dparams, grads)

        ] + [

            (dp,mu*dp – learning_rate*g) for dp, g in zip(dparams, grads)

        ] + [

           (self.We, We_update), (dWe, dWe_update)

        ]

       self.cost_predict_op = theano.function(

            inputs=[thX, thY],

            outputs=[cost, prediction],

            allow_input_downcast=True,

        )

       self.train_op = theano.function(

            inputs=[thX, thY],

            outputs=[cost, prediction],

            updates=updates

        )

Переходимк основному циклу.

        costs = []

       sequence_indexes = range(N)

        n_total= sum(len(y) for y in Y)

        for i inxrange(epochs):

            t0 =datetime.now()

           sequence_indexes = shuffle(sequence_indexes)

           n_correct = 0

            cost= 0

            it =0

            forj in sequence_indexes:

                c, p = self.train_op(X[j], Y[j])

               cost += c

               n_correct += np.sum(p == Y[j])

               it += 1

               if it % 200 == 0:

                   sys.stdout.write(“j/N: %d/%d correct rate so far: %f, cost so far:%f\r” % (it, N, float(n_correct)/n_total,cost))

                   sys.stdout.flush()

           print “i:”, i, “cost:”, cost, “correctrate:”, (float(n_correct)/n_total),“time for epoch:”, (datetime.now() – t0)

           costs.append(cost)

        ifshow_fig:

           plt.plot(costs)

           plt.show()

Функция score.

    def score(self, X, Y):

        n_total= sum(len(y) for y in Y)

       n_correct = 0

        for x, yin zip(X, Y):

            _, p= self.cost_predict_op(x, y)

           n_correct += np.sum(p == y)

        return float(n_correct) / n_total

Теперьпочти то же самое для F-меры.

    def f1_score(self, X, Y):

        P = []

        for x, yin zip(X, Y):

            _, p= self.cost_predict_op(x, y)

           P.append(p)

        Y =np.concatenate(Y)

        P =np.concatenate(P)

        returnf1_score(Y, P, average=None).mean()

Ипишем функцию main. У нас будет рекуррентная сеть с 10 узлами в слое,но я настоятельно рекомендую попробовать и другие значения.

def main():

    Xtrain,Ytrain, Xtest, Ytest, word2idx = get_data(split_sequences=True)

    V =len(word2idx) + 1

    rnn =RNN(10, [10], V, K)

   rnn.fit(Xtrain, Ytrain)

    print “trainscore:”, rnn.score(Xtrain, Ytrain)

    print “testscore:”, rnn.score(Xtest, Ytest)

    print “trainf1:”, rnn.f1_score(Xtrain, Ytrain)

    print “testf1:”, rnn.f1_score(Xtest, Ytest)

if __name__ == ‘__main__’:

main()

Разметка по частям речи. Скрытая марковская модель

Вэтой лекции мы реализуем разметку по частям речи, используя скрытые марковскиемодели.

Итак,начнём с импорта Numpy и Matplotlib. Кроме того, импортируем os и sys, посколькубудем использовать код, уже написанный на предыдущих занятиях. Из курса поскрытым марковским моделям импортируем HMM,но особо не беспокойтесь, если вы его ещё не изучали. И, разумеется, импортируем функцию get_data из pos_baseline.

import numpy as np

import matplotlib.pyplot as plt

import os

import sys

sys.path.append(os.path.abspath(‘..’))

from hmm_class.hmmd_scaled import HMM

from pos_baseline import get_data

from sklearn.utils import shuffle

from datetime import datetime

from sklearn.metrics import f1_score

Намнужна функция для вычисления точности, поскольку нет никакого реального способапроверить правильность скрытых состояний.

def accuracy(T, Y):

    n_correct =0

    n_total = 0

    for t, y inzip(T, Y):

       n_correct += np.sum(t == y)

        n_total+= len(y)

    return float(n_correct) / n_total

Разумеется,мы также вычислим F-меру.

def total_f1_score(T,Y):

    T =np.concatenate(T)

    Y =np.concatenate(Y)

    returnf1_score(T, Y, average=None).mean()

Инапишем функцию main. В ней будетпараметр сглаживания – для сглаживания наших матрицы перехода состояний и матрицынаблюдений.

def main(smoothing=10e-2):

    Xtrain,Ytrain, Xtest, Ytest, word2idx = get_data(split_sequences=True)

    V =len(word2idx) + 1

    M =max(max(y) for y in Ytrain) + 1

    A =np.ones((M, M))*smoothing

    pi =np.zeros(M)

    for y inYtrain:

        pi[y[0]]+= 1

        for i inxrange(len(y)-1):

            A[y[i], y[i+1]] += 1

Разобравшисьс циклом, нормализуем все распределения, чтобы их сумма равнялась единице.

    A /=A.sum(axis=1, keepdims=True)

    pi /= pi.sum()

ТеперьB.

    B = np.ones((M, V))*smoothing

    for x, y inzip(Xtrain, Ytrain):

        for xi,yi in zip(x, y):

           B[yi, xi] += 1

    B /= B.sum(axis=1, keepdims=True)

    hmm = HMM(M)

    hmm.pi = pi

    hmm.A = A

    hmm.B = B

Следующийэтап – создание прогноза. Для этого у нас уже есть функция get_state_sequence.

    Ptrain =[]

    for x inXtrain:

        p =hmm.get_state_sequence(x)

       Ptrain.append(p)

    Ptest = []

    for x inXtest:

        p =hmm.get_state_sequence(x)

        Ptest.append(p)

Ивыводим результат на экран.

    print(“trainaccuracy:”, accuracy(Ytrain, Ptrain))

   print(“test accuracy:”, accuracy(Ytest, Ptest))

   print(“train f1:”, total_f1_score(Ytrain, Ptrain))

   print(“test f1:”, total_f1_score(Ytest, Ptest))

if __name__ == ‘__main__’:

main()

Запустимпрограмму и проверим результат.

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

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