Распознавание именованных сущностей в обработке естественных языков

Распознавание именованных сущностей

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

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

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

Одноиз отличий между этой задачей и разметкой по частям речи состоит в крайнейнесбалансированности любых наших данных. Имеется в виду, что большинствотокенов не являются именованными сущностями, это просто обычные слова, как те, которыея сейчас произношу. Как вы могли заметить, почти 100% произносимых мною словявляются просто словами – это не чьё-то имя, географическое название иликомпания. И это то, что нельзя изменить.

Естьодин лёгкий способ распознавания именованных сущностей. В классических решенияхэтой задачи использовалось то обстоятельство, что они пишутся с большой буквы,и именно это показывало, является ли сущность именованной или нет. Это можнорасценить как своего рода обман. Для распознавания именованных сущностейпредпочтительней использовать структуру предложения, а не факт написания сбольшой буквы. Тем более, что в нынешнее время такой подход в любом случаеработать не будет. Почему? Потому что, например, в твиттере и в сообщенияхчатов люди часто не пишут собственные имена с большой буквы, так что мы неможем воспользоваться правилом «если токен начинается с большой буквы, товысока вероятность, что это имя собственное». Это даже не машинное обучение,это обычное программирование. В связи с этим в коде мы сначала все буквызаменим на малые. Мы также не будем рассматривать @имя_пользователя какнастоящие имена, поскольку это тоже обычное программирование, в котором можноочень легко достичь 100% точности.

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

Чтобы получить данные для этого примера, перейдите по ссылке https://github.com/aritter/twitter_nlp/blob/master/data/annotated/ner.txt. Просто загрузите этот файл и поместите в ту же папку, в которой будет код. Это сравнительно небольшой набор данных из твитов, и, вновь-таки, имеет очень простой формат: каждый токен идёт отдельной строкой с тэгом рядом.

Распознавание именованных сущностей. Базовая модель

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

Итак,импортируем библиотеки и класс LogisticRegression из моделиразметки по частям речи.

import numpy as np

from sklearn.utils import shuffle

from pos_baseline import LogisticRegression

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

def get_data(split_sequences=False):

    word2idx ={}

    tag2idx = {}

    word_idx = 0

    tag_idx = 0

    Xtrain = []

    Ytrain = []

    currentX =[]

    currentY =[]

    for line in open(‘ner.txt’):

        line =line.rstrip()

        if line:

            r =line.split()

           word, tag = r

            word= word.lower()

            if word 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

    print “numberof samples:”, len(Xtrain)

Данныенеобходимо перемешать, поскольку у нас только один входящий файл. Кроме того,нам самостоятельно придётся разделить данные на обучающий и проверочный наборы.Примем, что 30% данных будут проверочным набором.

    Xtrain, Ytrain = shuffle(Xtrain, Ytrain)

    Ntest = int(0.3*len(Xtrain))

    Xtest =Xtrain[:Ntest]

    Ytest =Ytrain[:Ntest]

    Xtrain =Xtrain[Ntest:]

    Ytrain =Ytrain[Ntest:]

    print “numberof classes:”, len(tag2idx)

    returnXtrain, Ytrain, Xtest, Ytest, word2idx, tag2idx

Далеепишем функцию main.

defmain():

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

    V =len(word2idx)

    print “vocabularysize:”, V

    K =len(tag2idx)

    model =LogisticRegression()

   model.fit(Xtrain, Ytrain, V=V,K=K, epochs=5)

    print“training complete”

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

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

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

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

if __name__ == ‘__main__’:

main()

Запустим программу и посмотрим, как она работает. Итак, видим функцию затрат и результат. F-мера получилась довольно малой.

Распознавание именованных сущностей. Рекуррентная нейронная сеть

Большая часть кода уже написана ранее, так что это будет очень короткий пример.

Итак, импортируем функцию get_data и класс RNN. На самом деле больше нам ничего и не нужно.

from ner_baseline import get_data

from pos_rnn import RNN

def main():

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

    V =len(word2idx)

    K =len(tag2idx)

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

   rnn.fit(Xtrain, Ytrain, epochs=70)

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

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

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

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

if __name__ == ‘__main__’:

main()

Итак, запустим программу и посмотрим, что получится.

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

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

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

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

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

Ау нас проблема гиперпараметров номер 2. Получится ли у вас улучшить модельрекуррентной нейронной сети, чтобы она работала лучше базовой модели логистическойрегрессии? Если да, какие значения гиперпараметров вы использовали? Заметьте,вам не нужно писать какой-то новый код – просто поменять ряд чисел. Если вамудастся найти такие значения гиперпараметров, которые дают хороший результат,пожалуйста, поделитесь ими на форуме.

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

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