Русский
Русский
English
Статистика
Реклама

Nlp

Собираем данные для тренировки в решении NLP-задач

21.01.2021 12:12:35 | Автор: admin

Выбор источника и инструментов реализации

В качестве источника информации я решил использоватьhabr.com коллективный блог с элементами новостного сайта (публикуются новости, аналитические статьи, статьи по информационным технологиям, бизнесу, интернету и др.). На этом ресурсе все материалы делятся на категории (хабы), из которых только основных 416 штук. Каждый материал может принадлежать к одной или нескольким категориям.

Код для сбора информации (парсинга) написан на языке python. Среда разработки Jupyter notebook на платформе Google Colab. Основные библиотеки:

  • BeautifulSoup парсер для синтаксического разбора файлов html / xml;

  • Requests инструмент для составления и обработки http запросов;

  • Re модуль для работы с регулярными выражениями;

  • Pandas высокоуровневый инструмент для управления данными.

Также использовал модуль tqdm для визуализации прогресса обработки и модуль ratelim для ограничения количества запросов к данным (чтобы не превысить лимит и не создавать излишнюю нагрузку на сервер).

Подробности реализации

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

mainUrl = 'http://personeltest.ru/aways/habr.com/ru/post/'postCount = 10000

Однако следует иметь ввиду, что некоторые публикации могут быть удалены авторами, либо перенесены в черновики, поэтому доступа к ним не будет. Для обработки таких случаев удобно использовать блокtry exceptв связке с библиотекойrequests. В общем виде процедура получения текста статьи может выглядеть так:

@ratelim.patient(1, 1)def get_post(postNum):currPostUrl = mainUrl + str(postNum)try:response = requests.get(currPostUrl)response.raise_for_status()response_title, response_post, response_numComment, response_rating, response_ratingUp, response_ratingDown, response_bookMark, response_views = executePost(response)dataList = [postNum, currPostUrl, response_title, response_post, response_numComment, response_rating, response_ratingUp, response_ratingDown, response_bookMark, response_views]habrParse_df.loc[len(habrParse_df)] = dataListexcept requests.exceptions.HTTPError as err:pass

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

В процедуре executePost описана обработка кода интернет-страницы для получения текста статьи и других необходимых параметров.

def executePost(page):soup = bs(page.text, 'html.parser')# Получаем заголовок статьиtitle = soup.find('meta', property='og:title')title = str(title).split('="')[1].split('" ')[0]# Получаем текст статьиpost = str(soup.find('div', id="post-content-body"))post = re.sub('\n', ' ', post)# Получаем количество комментариевnum_comment = soup.find('span', id='comments_count').textnum_comment = int(re.sub('\n', '', num_comment).strip())# Ищем инфо-панель и передаем ее в переменнуюinfo_panel = soup.find('ul', attrs={'class' : 'post-stats post-stats_post js-user_'})# Получаем рейтинг постаtry:rating = int(info_panel.find('span', attrs={'class' : 'voting-wjt__counter js-score'}).text)except:rating = info_panel.find('span', attrs={'class' : 'voting-wjt__counter voting-wjt__counter_positive js-score'})if rating:rating = int(re.sub('/+', '', rating.text))else:rating = info_panel.find('span', attrs={'class' : 'voting-wjt__counter voting-wjt__counter_negative js-score'}).textrating = - int(re.sub('', '', rating))# Получаем количество положительных и отрицательных голосов за рейтинг статьиvote = info_panel.find_all('span')[0].attrs['title']rating_upVote = int(vote.split(':')[1].split('и')[0].strip().split('')[1])rating_downVote = int(vote.split(':')[1].split('и')[1].strip().split('')[1])# Получаем количество добавлений в закладкиbookmk = int(info_panel.find_all('span')[1].text)# Получаем количество просмотров постаviews = info_panel.find_all('span')[3].textreturn title, post, num_comment, rating, rating_upVote, rating_downVote, bookmk, views

В ходе обработки использовалась библиотека BeautifulSoup для получения кода страницы в текстовом виде:soup=bs(page.text, html.parser). Затем использовал функции этой библиотекиfind/findallи другие для поиска определенных участков в коде (например, по имени класса или по html-тегам). Получив текст статьи обработал его регулярными выражениями для того, чтобы очистить от html-тегов, гиперссылок, лишних знаков и др.

Оформив обработку отдельной страницы можно запустить ее в цикле по всем статьям (или по необходимой выборке), размещенным на ресурсе. Например, можно взять первые 10 тысяч статей. Библиотека tqdm отобразит текущий прогресс выполнения.

for pc in tqdm(range(postCount)):postNum = pc + 1get_post(postNum)

Данные записывал в датафрейм pandas и сохранял в файл:

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

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

Подробнее..

Решаем NLP-задачу классификация текстов по темам

22.01.2021 12:16:53 | Автор: admin

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

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

Для решения нашей задачи снова используем язык программирования python и среду разработки Jupyternotebook на платформе GoogleColab.

В работе понадобятся следующие библиотеки:

  • scikit-learn свободно распространяемая библиотека на python, содержащая реализации различных методов машинного обучения;

  • nltk пакетбиблиотекдля символьной и статистическойобработки естественного языка;

  • matplotlib библиотека, содержащая набор инструментов для визуализации данных, понадобится для отображения облака слов.

Подробности реализации

Датасет сохранен в файле формата csv и содержит чуть более 8 тысяч записей. Для работы с ним будем использовать библиотеку pandas загружаем данные в память при помощи метода read_csv и отображаем на экране несколько первых строк:

import pandas as pddf_habr = pd.read_csv(habrParse.csv)df_habr.head()

Набор данных представляет собой таблицу, в первой колонке которой хранится текст статьи, во второй присвоенная категория (класс):

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

# Получение текстовой строки из списка словdef str_corpus(corpus):    str_corpus = ''    for i in corpus:        str_corpus += ' ' + i    str_corpus = str_corpus.strip()    return str_corpus# Получение списка всех слов в корпусеdef get_corpus(data):    corpus = []    for phrase in data:        for word in phrase.split():            corpus.append(word)    return corpus# Получение облака словdef get_wordCloud(corpus):    wordCloud = WordCloud(background_color='white',                              stopwords=STOPWORDS,                              width=3000,                              height=2500,                              max_words=200,                              random_state=42                         ).generate(str_corpus(corpus))    return wordCloudcorpus = get_corpus(df_train['text'].values)procWordCloud = get_wordCloud(corpus)fig = plt.figure(figsize=(20, 8))plt.subplot(1, 2, 1)plt.imshow(procWordCloud)plt.axis('off')plt.subplot(1, 2, 1)

Для необработанного набора данных облако слов содержит 243024 уникальных слова и выглядит так:

Попробуем очистить данные:

import nltknltk.download("stopwords")from nltk.corpus import stopwordsfrom string import punctuationrussian_stopwords = stopwords.words("russian")# Удаление знаков пунктуации из текстаdef remove_punct(text):    table = {33: ' ', 34: ' ', 35: ' ', 36: ' ', 37: ' ', 38: ' ', 39: ' ', 40: ' ', 41: ' ', 42: ' ', 43: ' ', 44: ' ', 45: ' ', 46: ' ', 47: ' ', 58: ' ', 59: ' ', 60: ' ', 61: ' ', 62: ' ', 63: ' ', 64: ' ', 91: ' ', 92: ' ', 93: ' ', 94: ' ', 95: ' ', 96: ' ', 123: ' ', 124: ' ', 125: ' ', 126: ' '}    return text.translate(table)habrParse_df['Post_clean'] = habrParse_df['Post'].map(lambda x: x.lower())habrParse_df['Post_clean'] = habrParse_df['Post_clean'].map(lambda x: remove_punct(x))habrParse_df['Post_clean'] = habrParse_df['Post_clean'].map(lambda x: x.split(' '))habrParse_df['Post_clean'] = habrParse_df['Post_clean'].map(lambda x: [token for token in x if token not in russian_stopwords\                                                                  and token != " " \                                                                  and token.strip() not in punctuation])habrParse_df['Post_clean'] = habrParse_df['Post_clean'].map(lambda x: ' '.join(x))

После небольшой очистки текстов от стоп-слов и знаков пунктуации количество уникальных слов снизилось до 142253, а облако слов стало более осмысленным:

При желании можно очистить тексты еще более качественно (в реальных задачах так и следует делать), но для обучающего примера датасет уже вполне подходит.

Посмотрим статистику по классам:

Видно, что некоторые классы представлены только одним элементом, а класс Чулан составляет более 65% датасета. Для того чтобы работать с более или менее сбалансированным датасетом, выберем тексты только четырех классов:

df_habr_clean = df_habr.loc[df_habr['hubs'].isin(['IT-компании', 'Habr', 'Управление медиа', 'Я пиарюсь'])]

Разделим датасет на тренировочную, тестовую и валидационную части:

from sklearn.model_selection import train_test_splitX_train, X_valid, y_train, y_valid = train_test_split(df_habr_clean ['Post_clean'], df_habr_clean ['hubs'], test_size=0.1, random_state=42)X_train, X_test, y_train, y_test = train_test_split(X_train, y_train, test_size=0.2, random_state=42)

Получили следующее соотношение выборок:X_train 1136 элементов,X_test 283 элемента,X_valid 158 элементов

Для дальнейшей работы понадобится импортировать несколько модулей из библиотеки scikit-learn:

from sklearn.pipeline import Pipeline# pipeline позволяет объединить в один блок трансформер и модель, что упрощает написание кода и улучшает его читаемостьfrom sklearn.feature_extraction.text import TfidfVectorizer# TfidfVectorizer преобразует тексты в числовые вектора, отражающие важность использования каждого слова из некоторого набора слов (количество слов набора определяет размерность вектора) в каждом текстеfrom sklearn.linear_model import SGDClassifierfrom sklearn.neighbors import KNeighborsClassifier# линейный классификатор и классификатор методом ближайших соседейfrom sklearn import metrics# набор метрик для оценки качества моделиfrom sklearn.model_selection import GridSearchCV# модуль поиска по сетке параметров

Сначала создадим 2 классификатора (чтобы можно было в дальнейшем сравнить качество получившихся моделей) и обучим их на тестовых данных:

sgd_ppl_clf = Pipeline([    ('tfidf', TfidfVectorizer()),    ('sgd_clf', SGDClassifier(random_state=42))])knb_ppl_clf = Pipeline([    ('tfidf', TfidfVectorizer()),    ('knb_clf', KNeighborsClassifier(n_neighbors=10))])sgd_ppl_clf.fit(X_train, y_train)knb_ppl_clf.fit(X_train, y_train)

Предскажем получившимися моделями класс текстов в тестовой выборке и сравним метрики:

predicted_sgd = sgd_ppl_clf.predict(X_test)print(metrics.classification_report(predicted_sgd, y_test))
predicted_sgd = knb_ppl_clf.predict(X_test)print(metrics.classification_report(predicted_sgd, y_test))

В связи с тем, что датасет не сбалансирован, метрику accuracy (доля верных ответов) использовать нельзя, так как это приведет к завышенной оценке качества работы классификатора. В данном случае самое правильное считать сразу несколько метрик, устойчивых к распределению классов (в данном случае, это точность, полнота и f-мера) и смотреть на них все. Однако часто бывает удобно получить не большой набор цифр, а одно число, по которому можно понять, насколько хорошо модель работает. В нашей задаче лучше всего подходит macro-avg (сначала подсчитывается каждая метрика по каждому классу, а потом усредняется). Macro-avg более устойчива к скошенным распределениям классов.

Линейный классификатор показал лучший результат на тестовых данных (0,77 против 0,72 у классификатора методом ближайших соседей), поэтому для дальнейшей настройки модели будем использовать его.

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

Попробуем улучшить нашу модель, используя различные параметры. Следует иметь в виду, что для доступа к параметрам объекта pipeline необходимо указывать их в виденазвание объекта__название параметра:

Обучим модель, используя предложенные параметры (часть из них получила значение по умолчанию, поэтому их указывать не обязательно), и оценим ее качество на тестовых данных:

sgd_ppl_clf = Pipeline([    ('tfidf', TfidfVectorizer(ngram_range=(1, 2))),    ('sgd_clf', SGDClassifier(penalty='elasticnet', class_weight='balanced', random_state=42))])sgd_ppl_clf.fit(X_train, y_train)predicted_sgd = sgd_ppl_clf.predict(X_test)print(metrics.classification_report(predicted_sgd, y_test))

Значение целевой метрики выросло.

predicted_sgd_val = sgd_ppl_clf.predict(X_valid)print(metrics.classification_report(predicted_sgd_val, y_valid))

На валидационной выборке качество также выросло (0,8 против 0,76 с использованием стандартных параметров классификатора), следовательно, мы успешно справились с поставленной задачей.

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

Надеюсь, статья была полезной и поможет вам начать самостоятельно решать nlp-задачи.

Подробнее..

NLP ВДЕЛЯЕМ ФАКТ ИЗ ТЕКСТОВ С ПОМОЩЬЮ ТОМИТА-ПАРСЕРА

17.03.2021 10:08:48 | Автор: admin

NLP - natural language processing

Большая часть данных в мире не структурирована это просто тексты на русском или на любом другом языке. Извлеченные факты из таких текстов могут представлять особый интерес для бизнеса, поэтому подобные задачи возникают сплошь и рядом. Этим вопросом занимается отдельное направление искусственного интеллекта: обработка естественного языка, тот самый NLP (Natural Language Processing).

Существует много способов выделить факты из текста и у всех свои плюсы и минусы:

  • Регулярные выражения

Высокая скорость работы и стабильность нивелируется сложностью синтаксиса и низким покрытием.

  • Нейронные сети

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

  • КС-грамматики

Предсказуемость результата, легко писать правила, но сложно запускать в ПРОМе

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

Исходный текст

Оплата за аренду торгового места 2 по адресу ул Маршала Жукова,15 за июнь 2020г., в сумме 5400,00 р

Перечисление денежных средств на Шустрик У.С. в субаренду автомобиля в сумме 15299,00 руб, без НДС

Оплата за найм общежития 575 по адресу пр Обуховской обороны, 145 от 17.09.2020 за февраль 2020 года, с ндс 18% 5300 рублей.

Оплата за аренду нежилого помещения по адресу Малая Садовая ул, 23, договор 51 от 01.09.2020 7500 рублей.

Частичная оплата по Договору аренды 1-03а от 01.07.2020 за аренду помещения по проспекту Дальневосточный 211 в октябре 2020 Сумма 23000 в т.ч.НДС(18%)

Что такое Томита-парсер?

Томита-парсер это инструмент для извлечения структурированных данных (фактов) из русского текста, позволяющий создавать и быстро прототипировать систему извлечения фактов с помощью шаблонов (контекстно-свободных грамматик) и словарей ключевых слов. Исходный код проекта открыт и размещен наGitHub, собственно отсюда мы скачиваем проект и проводим сборку для дальнейшей работы.

Где можно использовать Томита-парсер?

  • Обработка транзакций аренда, покупка

  • Обработка транскрипций звонков выставление задач

  • Новостной мониторинг оценка состояния кредитующейся компании

  • Парсинг текста резюме автоматизация выделения навыка и опытов кандидата

  • Парсинг текста судебных дел

Как работает Томита-парсер?

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

Для запуска необходима сама программа tomitaparser.exe (рекомендации по сборке см.здесь) и следующие файлы:

  • config.proto конфигурационный файл парсера. Сообщает парсеру всю информацию о том, где искать все остальные файлы и как их интерпретировать. Этот файл обязательный и выступает в роли единственного аргумента для tomitaparser.exe;

  • dic.gzt корневой словарь. Содержит перечень всех используемых в проекте словарей и грамматик. Другими словами, это некий агрегатор, который собирает все, что создается в рамках проекта. Без этого файла парсер работать не будет;

  • mygram.cxx грамматика. Содержит набор правил, которые описывают текстовые цепочки. Таких файлов может быть несколько. Взаимодействует с парсером через корневой словарь;

  • facttypes.proto описание типов фактов;

  • kwtypes.proto описание типов ключевых слов. Нужен, если в проекте создаются новые типы ключевых слов.

Все эти файлы необходимо начинать с явного указания кодировкиutf8везде, где планируется использовать кириллические символы (русский текст).

Корневой словарь

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

encoding "utf8"; // явно указываем кодировку// импортируем зашитые в парсер файлы с базовыми типами, используемыми в словарях и грамматикахimport "base.proto";import "articles_base.proto";// оставляем ссылку на нашу грамматикуTAuxDicArticle "payment" {    key = { "tomita:mygram.cxx" type=CUSTOM }};

Грамматика

Для создания своей грамматики разберемся с простейшими правилами и понятиями. Томитные грамматики работают с цепочками, где грамматика это набор правил, которые описывают цепочки слов в тексте. Грамматика пишется на специальном формальном языке. Структурно правило разделяется символом ->на левую и правую части. В левой части указывается один терминал, в правой последовательность терминалов и нетерминалов. Нетерминал строится из терминалов и должен хотя бы один раз встретиться в правой части правила. Если нетерминал встречается только в левой части это означает вершину грамматики. В роли терминалов выступают названия частей речи (Noun, Verb, Adj), символы (Comma, Punct, Ampersand, PlusSign) и леммы. Полный перечень терминалов см. поссылке.

Правая часть правила может сопровождаться операторами. Например, оператор +после (не)терминала означает, что символ повторяется один или более раз. Этот и другие операторы подробно описаны вдокументации.

Для наложения ограничений на (не)терминал используются специальные пометы, которые уточняют свойства (не)терминала, например, определение регистра символов или связей по роду и падежу между словами. Записываются пометы после (не)терминала в угловых скобках< >через запятую.С полным перечнем ограничений-помет можно ознакомится поссылке. Теперь, когда мы обладаем необходимым теоретическим минимумом создадим в папке с парсером файл формата cxx, где мы будем описывать свою грамматику mygram.cxx. Ссылку на этот файл мы уже оставили в корневом словаре. Для начала создадим правило для выделения назначения платежа. В нашем случае наименование оплаченного объекта это существительное, перед которым может стоять прилагательное, стоящее за словами аренда, субаренда, найм.

#encoding "utf8" // явно указываем кодировку// оператор "|" работает аналогично оператору "или"Rent -> 'аренда' | 'субаренда' | 'найм';// оператор "" означает, что символ может встречаться в тексте 0 или более раз// помета <gnc-agr[1]> говорит о том, что прилагательное должно быть согласовано с существительным по роду, числу и падежуPurpose -> Rent Adj<gnc-agr[1]> Noun<gnc-agr[1]>;

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

// в нетерминале StreetW указываем названия дескрипторов улицы, а в StreetAbbr - перечисляем известные сокращенияStreetW -> 'улица' | 'проспект' | 'шоссе' | 'линия';StreetAbbr -> 'ул' | 'пр' | 'просп' | 'пр-т' | 'ш';// объединяем два нетерминала в один нетерминал StreetDescr, который будет обозначать либо полнозначную лемму StreetW либо сокращение StreetAbbrStreetDescr -> StreetW | StreetAbbr;StreetNameNoun -> (Adj<gnc-agr[1]>) Word<gnc-agr[1], rt> (Word<gram="род">);StreetNameAdj -> Adj<h-reg1> Adj*;

Нетерминалом StreetNameNounмы описали названия улиц, выраженных существительным. Основным элементом в данной цепочке выступает слово, для этого, обозначаем его пометой <rt>. Перед ним опционально может стоять или не стоять прилагательное, согласованное по роду, числу и падежу. После основного слова может стоять или не стоять слово в родительном падеже, например, пр. Обуховской обороны. Чтобы указать на то, что прилагательное и слово в родительном падеже слева и справа от основного текста являются опциональными, т.е. не обязательными, используем оператор (). Нетерминал StreetNameAdjописывает названия улиц, выраженных прилагательным. Первое прилагательное в такой цепочке начинается с большой буквы. Добиваемся этого результата благодаря помете <h-reg1>. Далее может встречаться еще некоторое количество прилагательных, для этого применяем оператор *. Переходим к описанию правил, определяющих адрес.

Address -> StreetDescr StreetNameNoun<gram="род", h-reg1>;Address -> StreetDescr StreetNameNoun<gram="им", h-reg1>;Address -> StreetNameAdj<gnc-agr[1]> StreetW<gnc-agr[1]>;Address -> StreetNameAdj StreetAbbr;

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

// добавляем список месяцев в корневой словарь dic.gztTAuxDicArticle "month" {    key = { "январь" | "февраль" | "март" | "апрель" | "май" | "июнь" | "июль" | "август" | "сентябрь" | "октябрь" | "ноябрь" | "декабрь" }};

В файл с грамматикой добавляем следующее:

Month -> Noun<kwtype="month">;Year -> AnyWord<wff=/[1-2]?[0-9]{1,3}г?\.?/>;Period -> Month Year;

Пометка kwtype означает, что существительное ограничено статьей month в корневом словаре, а благодаря регулярным выражениям мы выделяем год как число от 0 до 2999 с возможными г или г. в конце. Переходим к определению корневого нетерминала, который соберет вместе все созданные ранее правила. Корневой нетерминал назовем Result и составим несколько возможных вариантов:

Result -> Purpose AnyWord* Address AnyWord* Period;Result -> Purpose AnyWord* Address;Result -> Purpose;

Терминал AnyWord с оператором * означает, что между соседними нетерминалами может встречаться любая последовательность символов 0 или более раз. В первой цепочке встречаются все выделенные нами атрибуты: назначение, адрес и период. Во второй: назначение и адрес, а в третьей только назначение.

Факты

На данном этапе мы научили парсер выделять цепочки слов в тексте. Для извлечения фактов из полученных цепочек создаем отдельный файл facttypes.proto и сразу же добавляем в корневой словарь dic.gztстрочку с импортом (помним, что корневой словарь- это агрегатор всего, что создается в проекте).

import "facttypes.proto"; // импортируем в словарь dic.gzt факты

В файле facttypes.proto определяем новый факт Paymentи добавляем три атрибута (поля): назначение, адрес и период платежа. Запишем в файл следующее:

// импорт базовых типовimport "base.proto";import "facttypes_base.proto";message Payment: NFactType.TFact {    required string Purpose = 1;    optional string Address = 2;    optional string Period = 3;};

Факт Payment наследуется от базового типа NFactType.TFact, а required и optional означает, что атрибут является обязательными или опциональным соответственно. Для того, чтобы интерпретировать подцепочку в факт, необходимо написать слово interp и после него в скобках указать имя факта и имя поля, в которое должна попасть подцепочка. Теперь внесем изменения в корневые правила грамматики, добавив процедуру интерпретации.

// подцепочка Purpose интерпретируется в поле Purpose факта Payment// подцепочка Address интерпретируется в поле Address факта Payment// подцепочка Period интерпретируется в поле Period факта PaymentResult -> Purpose interp(Payment.Purpose) AnyWord* Address interp(Payment.Address) AnyWord* Period interp(Payment.Period);Result -> Purpose interp(Payment.Purpose) AnyWord* Address interp(Payment.Address);Result -> Purpose interp(Payment.Purpose);

Конфигурационный файл

Далее создаем конфигурационный файл и сообщаем парсеру, где искать исходный текст, куда записывать результат, какие грамматики использовать и какие факты извлекать.

encoding "utf8"; // явно указываем кодировкуTTextMinerConfig {    // указываем корневой словарь    Dictionary = "dic.gzt";    // входные данные    Input = {File = "input.txt"}    // указываем куда записывать результат работы парсера    Output = {File = "output.txt"            Format = text}    // грамматики, которые будут использоваться при парсинге    Articles = [        { Name = "payment" }        ]    // факты, которые извлекаем    Facts = [        { Name = "Payment" }        ]    // показать отладочный вывод с результатами работы грамматики    PrettyOutput = "pretty.html"}

Запуск парсера

Запускается парсер из командной строки:

> tomitaparser.exe config.proto

В файл input.txt мы поместили исходный текст, размещенный в самом начале статьи. После работы парсер записал результат в файл output.txt:

Оплата за аренду торгового места  2 по адресу ул Маршала Жукова , 15 за июнь 2020г . , в сумме 5400,00 р     Payment    {        Purpose = аренда торгового места        Address = ул Маршала Жукова        Period = июнь 2020г    }Перечисление денежных средств на Шустрик У. С. в субаренду автомобиля в сумме 15299,00 руб , без НДС     Payment    {        Purpose = субаренда автомобиля    }Оплата за найм общежития  575 по адресу пр Обуховской обороны , 145 от 17.09.2020 за февраль 2020 года , с ндс 18% - 5300 рублей .     Payment    {        Purpose = найм общежития        Address = пр Обуховской обороны        Period = февраль 2020    }Оплата за аренду нежилого помещения по адресу Малая Садовая ул , 23 , договор 51 от 01.09.2020 - 7500 рублей .     Payment    {        Purpose = аренда нежилого помещения        Address = Садовая ул    }Частичная оплата по Договору аренды  1-03а от 01.07.2020 за аренду помещения по проспекту Дальневосточный 211 в октябре 2020 Сумма 23000 в т.ч.НДС ( 18% )     Payment    {        Purpose = аренда помещения        Address = проспект Дальневосточный        Period = октябрь 2020    }

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

Подробнее..

Анализ сообщений коммерческого чата на предмет игнорирования вопроса клиента на основе модели nlp

07.06.2021 12:06:42 | Автор: admin

Задача Провести анализ сообщений коммерческого чата на предмет игнорирования вопроса клиента менеджером компании

На входе: лог чатов с клиентом компании в csv формате:

Дата отправки

Сообщение

Кто отправил

Номер заявки

yyyy-mm-dd hh-mm-ss

Текст1

Отправитель1

Номер1

yyyy-mm-dd hh-mm-ss

Текст2

Отправитель2

Номер2

yyyy-mm-dd hh-mm-ss

Текст3

Отправитель3

Номер3

yyyy-mm-dd hh-mm-ss

Текст4

Отправитель4

Номер4

yyyy-mm-dd hh-mm-ss

текстN

отправительN

НомерN

План решения:

1. Подготовка данных

2. Выбор инструмента для определения похожих сообщений внутри каждого чата

3. Анализ полученных результатов

4. Подведение итогов

Подготовка данных

Применяются следующие инструменты:

%matplotlib inlineimport matplotlib.pyplot as pltimport seaborn as snsimport tqdmimport pandas as pdimport numpy as npimport reimport timefrom nltk.tokenize import sent_tokenize, word_tokenizeimport pymorphy2morph = pymorphy2.MorphAnalyzer(lang='ru')from nltk import edit_distanceimport editdistanceimport textdistancefrom jellyfish import levenshtein_distance

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

df = pd.DataFrame()counter = 1for path_iter in PATH:    print(path_iter)    df_t = pd.read_csv(path_iter, sep=';', encoding='cp1251', dtype='str', engine = 'python')    if counter == 1:        df_t['Дата отправки'] = pd.to_datetime(df_t['Дата отправки'], format='%d.%m.%y %H:%M')    else:        df_t['Дата отправки'] = pd.to_datetime(df_t['Дата отправки'], format= '%Y-%m-%d %H:%M:%S')    df = df.append(df_t)    counter += 1df.sort_values(by=['Номер заявки', 'Дата отправки'], inplace=True)df.reset_index(drop=True, inplace=True)print('Размер DF, rows =', len(df))df.head()

>>>

Размер DF, rows = 144584

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

df['Кто отправил'].value_counts()>>>['AGENT']         43137['CONSULTANT']    33040['USER']          29463['MANAGER']       21257[]                13939['BOT']            3748Name: Кто отправил, dtype: int64
print('Кол-во уникальных чатов', len(set(df['Номер заявки'].tolist())))>>>Количество уникальных чатов 5406

Размер большинства чатов 25 сообщений.

df['Номер заявки'].value_counts().hist(bins = 40)

Проведена обработка текста: нижний регистр, игнорирование сообщений с одним словом, оставляем только русские буквы, пробел и тире. Удалены из сообщений тексты о вложениях документов вида картинка.jpg.

def filter_text(text):    '''    принимает текст,    на выходе обработанные текст.        в тексте остаются только символы русского алфавита, тире, пробел    '''    text = text.lower()    if len(re.findall(r'[\w]+', text)) == 1:        #print(re.findall(r'[\w]+', text), '---', len(re.findall(r'[\w]+', text)))        return ''    #удаляем из сообщений тексты с вложениями документов вида "картинка.jpg"    text = re.sub(r'(.+[.]jpg)|(.+[.]pdf)', '', text)    text = [c for c in text if c in 'абвгдеёжзийлкмнопрстуфхцчшщъыьэюя- ']    text = ''.join(text)    return textdf['work_text'] = df.apply(lambda row: filter_text(row['Сообщение']), axis = 1)

Заменяем несколько пробелов одним пробелом.

df['work_text'] = df.apply(lambda row: re.sub(r'\s+', ' ', row['work_text']) , axis = 1)

Чистим от частотных/не значимых словосочетаний:

STOP_WORDS = [    '-', '--', '-а', '-б', '-в', '-е', '-й', '-к', '-м', '-т', '-у', '-х', '-ю', '-я', 'а', 'а-', 'аа', 'аб',     'ав', 'ад', 'ае', 'аж', 'ак', 'ам', 'ан', 'ао', 'ап', 'ас', 'ау', 'ач', 'б', 'ба', 'бв', 'бд', 'без', 'бы',     'в', 'в-', 'ва', 'вб', 'вв', 'вг', 'ви', 'вм', 'вн', 'во', 'вп', 'вс', 'вт', 'вх', 'вщ', 'вы', 'вю', 'г',     'г-', 'га', 'гв', 'гк', 'гм', 'го', 'гп', 'гу', 'д', 'да', 'дб', 'дв', 'дд', 'де', 'день', 'ди', 'дк', 'для',     'до', 'доброе', 'добрый', 'др', 'дс', 'ду', 'дю', 'дя', 'е', 'еа', 'ев', 'ее', 'ей', 'ен', 'ес', 'ею', 'её',     'ж', 'же', 'жк', 'жр', 'з', 'за', 'здравствуйте', 'зп', 'зу', 'и', 'иа', 'из', 'ик', 'ил', 'или', 'им', 'ин',     'ио', 'ип', 'иу', 'их', 'й', 'к', 'ка', 'как', 'кв', 'кд', 'кк', 'км', 'ко', 'кп', 'кр', 'кс', 'л', 'ла',     'лд', 'ли', 'лк', 'ля', 'м', 'ма', 'мб', 'мв', 'мг', 'мз', 'ми', 'мк', 'мл', 'мм', 'мне', 'мо', 'мр', 'мс',     'мт', 'му', 'мы', 'мэ', 'мю', 'мя', 'н', 'на', 'нв', 'не', 'ни', 'нн', 'но', 'нп', 'нс', 'ну', 'нф', 'ны',     'ня', 'о', 'оа', 'об', 'ов', 'од', 'ое', 'ой', 'ок', 'ом', 'он', 'оо', 'оп', 'ос', 'от', 'ох', 'оч', 'ою',     'п', 'пв', 'пи', 'пк', 'пл', 'пм', 'пн', 'по', 'пожалуйста', 'пп', 'пр', 'при', 'пс', 'пт', 'пф', 'пш',     'пю', 'р', 'ра', 'рб', 'рв', 'рд', 'ри', 'рн', 'ро', 'рп', 'рр', 'рс', 'рт', 'ру', 'рф', 'с', 'са', 'сб',     'св', 'сг', 'се', 'сж', 'ск', 'сл', 'см', 'сн', 'со', 'сп', 'спасибо', 'ср', 'ст', 'су', 'сх', 'сч', 'т',     'т-', 'та', 'тб', 'тв', 'тг', 'тд', 'те', 'ти', 'тк', 'тн', 'то', 'тп', 'тр', 'ту', 'тц', 'тч', 'ты', 'у',     'ув', 'уж', 'ук', 'ул', 'ур', 'утро', 'ух', 'уч', 'уя', 'ф', 'фг', 'фд', 'фз', 'фн', 'фф', 'фц', 'х', 'хг',     'хм', 'хо', 'ц', 'цб', 'цн', 'ч', 'чб', 'че', 'чп', 'чс', 'чт', 'что', 'ш', 'ы', 'ь', 'э', 'эг', 'эл', 'эп',     'эр', 'эт', 'эх', 'ю', 'юа', 'юв', 'юг', 'юл', 'юр', 'юс', 'я', 'яг', 'яя', 'ё',    'я', 'сейчас', 'это', 'ещё', 'понятно', 'отлично', 'извинить','извините','где']#DF только с сообщениями клиентовdf_users = df[df['Кто отправил'] == '''['USER']'''][df_users.work_text.replace(x, '', regex=True, axis = 1, inplace=True) for x in STOP_SENTS]

Приводим слова к нормальной форме, удалим из текста частотные слова

def normalize_text(text):    '''    идем по всем токенам,     то что в STOP_WORDS - выкидываем,    меняем каждый на нормальную форму,    соединяем пробелом,    возвращаем текст    '''    ls = list()    for word in word_tokenize(text, language='russian'):        if word not in STOP_WORDS:            ls.append(morph.parse(word)[0].normal_form)    norm_text = ' '.join([x for x in ls])    return norm_textdf_users['clear_text'] = df_users.work_textdf_users['clear_text'] = df_users.apply(lambda row: normalize_text(row.clear_text), axis = 1)

Выбор инструмента для поиска похожих сообщений внутри каждого чата

Найдем расстояние Левенштейна всех заявок по всем текстам:

def get_edit_distance(sec_posts, val_leven):    '''    sec_posts - list с постами чата    val_leven - коэффициент редакционного расстояния по Левенштейну:                 ratio_ed = editdistance / длину текста    '''    ls = []    len_sec_posts = len(sec_posts)    sec_posts_iter = 0    for i in sec_posts:        sec_posts_iter += 1 #не проходим по тем элементам, которые уже сравнили        for k in sec_posts[sec_posts_iter:]:            #ed = edit_distance(i, k)            #ed = textdistance.levenshtein(i, k)            #ed = levenshtein_distance(i, k)            ed = editdistance.eval(i, k)            if len(k) == 0:                ratio_ed = 0            else:                ratio_ed = ed / len(k)            if len(k) !=0 and len(i) != 0 and ratio_ed <= val_leven:                ls.append([i, k, ratio_ed, ed])    #list [post1, post2, ratio_ed, ed]    return lsCURRENT_LEV_VALUE = 0.25#Уникальные номера заявок:number_orders = set(df_users[(df_users['Кто отправил'] == '''['USER']''')]['Номер заявки'].tolist())#Найдем расстояния Левенштейна по всем фразам, всех заявок:all_dic = {}for i_order in tqdm.tqdm(number_orders):      posts_list = df_users[(df_users['Кто отправил'] == '''['USER']''') & (df_users['Номер заявки'] == i_order)]['clear_text'].tolist()    all_dic[i_order] = get_edit_distance(posts_list, CURRENT_LEV_VALUE)

В результате тестирования была обнаружена самая производительная библиотека для расчета редакционного расстояния Левенштейна editdistance.

Расчет затраченного времени в секундах на обработку 29463 сообщений чата представлен ниже. В тесте участвовалиimport edit_distance, import editdistance, import textdistance, from jellyfish import:

Библиотека editdistance производительнее аналогов от 18 до 31 раза.

Чтобы определить допустимую схожесть текстов используем метрику CURRENT_LEVEN, которая ограничит допустимое значение отношения редакционного расстоянию двух сравниваемых текстов к длине первого текста editdistance (text1, text2)/ длину текста(text1).

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

Анализ полученных результатов

Формируется dataframe из расчетных данных:

df_rep_msg = pd.DataFrame(ls, columns=['id_order', 'clear_msg', 'clear_msg2', 'ratio_dist', 'ed_dist'])df_rep_msg['id_rep_msg'] =  df_rep_msg.reset_index()['index'] +1 df_rep_msg.head()

Разворот сообщений в строки:

df1 = df_rep_msg[['id_order','clear_msg','ratio_dist','ed_dist', 'id_rep_msg']]df2 = df_rep_msg[['id_order','clear_msg2','ratio_dist','ed_dist', 'id_rep_msg']]df2.columns = ['id_order','clear_msg','ratio_dist','ed_dist','id_rep_msg']df_rep_msg = pd.concat([df1, df2], axis=0).sort_values(by=['id_order'], ascending=[True])del df1del df2df_rep_msg.drop_duplicates(inplace=True)df_rep_msg.head(10)

Добавляются признаки к датафрейму df_users_rep_msg

df_users_rep_msg = pd.merge(    df_users, df_rep_msg, how='left',     left_on=['clear_text','Номер заявки'],    right_on=['clear_msg','id_order'])df_users_rep_msg[df_users_rep_msg.clear_msg.notnull()][    ['Дата отправки', 'Сообщение', 'Кто отправил', 'Номер заявки', 'clear_msg', 'ratio_dist', 'ed_dist','id_rep_msg']].head()

Посмотрим на сообщения, количество которых повторяются более 6 раз в одном чате

count_ser = df_users_rep_msg[df_users_rep_msg.id_rep_msg.notnull()]['id_rep_msg'].value_counts()filt = count_ser[count_ser > 4]filt
df_users_rep_msg[df_users_rep_msg.id_rep_msg.isin(filt.index)][['Дата отправки','Сообщение','id_order']]

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

Разметим основной дата фрейм

df_m = pd.merge(    df, df_users_rep_msg[df_users_rep_msg.id_rep_msg.notnull()],     how='left',     left_on  = ['Дата отправки','Кто отправил',  'Номер заявки', 'Сообщение'],    right_on =['Дата отправки','Кто отправил', 'Номер заявки', 'Сообщение'])df_m = df_m[['Дата отправки', 'Сообщение', 'Кто отправил', 'Номер заявки','clear_msg',       'ratio_dist', 'ed_dist', 'id_rep_msg']]df_m.loc[18206:18216]

Получим минимальный и максимальный индекс по каждому совпавшему сообщению

df_temp = df_m[df_m.id_rep_msg.notnull()][['id_rep_msg']]df_temp['id_row'] = df_temp.index.astype('int')df_temp.id_rep_msg = df_temp.id_rep_msg.astype('int')index_arr = df_temp.groupby(by = 'id_rep_msg')['id_row'].agg([np.min, np.max]).valuesindex_arr[0:10]>>>array([[ 36383,  36405],       [108346, 108351],       [    12,     43],       [ 99376,  99398],       [111233, 111235],       [121610, 121614],       [ 91234,  91252],       [ 11963,  11970],       [  7103,   7107],       [ 53010,  53016]], dtype=int64)df_m.loc[index_arr[194][0]:index_arr[194][1]]

В примере ниже видно, как обращение перехватил бот/автоматизированная система, клиент не игнорирован

df_m.loc[index_arr[194][0]:index_arr[194][1]]

Проверяем остальные случаи

#Проверим остальные случаиresults = {'Ответ_получен':0, 'Ответ_проигнорирован':0, 'Всего_групп_повторных_сообщений':0}results['Всего_групп_повторных_сообщений'] = len(index_arr)for i in range(len(index_arr)):    if len(set(df_m.loc[index_arr[i][0]:index_arr[i][1]]['Кто отправил'].tolist()) - set(["['USER']"])) > 0:        results['Ответ_получен'] += 1    elif len(set(df_m.loc[index_arr[i][0]:index_arr[i][1]]['Кто отправил'].tolist()) - set(["['USER']"])) == 0:        results['Ответ_проигнорирован'] += 1print('Доля проигнорированных сообщений:', round(100*(results['Ответ_проигнорирован']/results['Всего_групп_повторных_сообщений']), 2), '%')results

Количество обработанных сообщений:

N = 1anw_yes = (236)anw_no = (103)ind = np.arange(N)    width = 0.35p1 = plt.bar(ind, anw_yes, width)p2 = plt.bar(ind, anw_no, width, bottom=anw_yes)plt.ylabel('Повторных_сообщений')plt.title('Доля обработанных сообщений')plt.xticks(ind, (''))plt.yticks(np.arange(0, 500, 50))plt.legend((p1[0], p2[0]), ('Ответ_получен', 'Ответ_проигнорирован'))plt.show()

Заключение

С помощью NLP модели, построенной на измерении редакционного расстояния по Левенштейну, удалось сократить кол-во проверяемых чатов с 5406 ед. до 339 ед. Из них определить высоко-рисковые чаты 103 ед. Определить и использовать в расчетах высокопроизводительную библиотеку для расчета дистанции редактирования между текстами, позволяющую масштабировать проверку на большие объемы информации.

Подробнее..

Маленький и быстрый BERT для русского языка

10.06.2021 02:22:28 | Автор: admin

BERT нейросеть, способная весьма неплохо понимать смысл текстов на человеческом языке. Впервые появивишись в 2018 году, эта модель совершила переворот в компьютерной лингвистике. Базовая версия модели долго предобучается, читая миллионы текстов и постепенно осваивая язык, а потом её можно дообучить на собственной прикладной задаче, например, классификации комментариев или выделении в тексте имён, названий и адресов. Стандартная версия BERT довольно большая: весит больше 600 мегабайт, обрабатывает предложение около 120 миллисекунд (на CPU). В этом посте я предлагаю уменьшенную версию BERT для русского языка 45 мегабайт, 6 мс на предложение. Уже есть tinybert для английского от Хуавея, есть моя уменьшалка FastText'а, а вот маленький (англо-)русский BERT, кажется, появился впервые. Но насколько он хорош?

Дистилляция путь к маленькости

Чтобы создать маленький BERT, я решил обучить свою нейросеть, используя при этом уже готовые модели в качестве учителей. Сейчас объясню подробнее.

Если очень коротко, то BERT работает так: сначала токенизатор разбивает текст на токены (кусочки размером от одной буквы до целого слова), от них берутся эмбеддинги из таблицы, и эти эмбеддинги несколько раз обновляются, используя механизм self-attention для учёта контекста (соседних токенов). При предобучении классический BERT выполняет две задачи: угадывает, какие токены в предложении были заменены на специальный токен [MASK], и шли ли два предложения следом друг за другом в тексте. Как потом показали, вторая задача не очень нужна. Но токен [CLS], который ставится перед началом текста и эмбеддинг которого использовался для этой второй задаче, употреблять продолжают, и я тоже сделал на него ставку.

Дистилляция способ перекладывания знаний из одной модели в другую. Это быстрее, чем учить модель только на текстах. Например, в тексте [CLS] Ехал Грека [MASK] реку "верное" решение поставить на место маски токен через, но большая модель знает, что токены на, в, за в этом контексте тоже уместны, и это знание полезно для обучения маленькой модели. Его можно передать, заставляя маленькую модель не только предсказывать высокую вероятность правильного токена через, а воспроизводить всё вероятностное распределение возможных замаскированных токенов в данном тексте.

В качестве основы для модели я взял классический bert-multilingual (веса), ибо хочу, чтобы модель понимала и русский, и английский, и его же использую на ранних стадиях дистилляции как учителя по распределению токенов. Словарь этой модели содержит 120К токенов, но я отобрал только те, которые часто встречаются в русском и английском языках, оставив 30К. Размер эмбеддинга я сократил с 768 до 312, число слоёв с 12 до 3. Эмбеддинги я инициализировал из bert-multilingual, все остальные веса случайным образом.

Поскольку я собираюсь использовать маленький BERT в первую очередь для классификации коротких текстов, мне надо, чтобы он мог построить хорошее векторное представление предложения. Поэтому в качестве учителей для дистилляции я выбрал модели, которые с этим здорово справляются: RuBERT (статья, веса), LaBSE (статья, веса), Laser (статья, пакет) и USE (статья, код). А именно, я требую, чтобы [CLS] эмбеддинг моей модели позволял предсказать эмбеддинги предложений, полученные из этих трёх моделей. Дополнительно я обучаю модель на задачу translation ranking (как LaBSE). Наконец, я решил, что неплохо было бы уметь полностью расшифровывать предложение назад из CLS-эмбеддингов, причём делать это одинаково для русских и английских предложений как в Laser. Для этих целей я примотал изолентой к своей модели декодер от уменьшенного русского T5. Таким образом, у меня получилась многозадачная модель о восьми лоссах:

  • Обычное предсказание замаскированных токенов (я использую full word masks).

  • Translation ranking по рецепту LaBSE: эмбеддинг фразы на русском должен быть ближе к эмбеддингу её перевода на английский, чем к эмбеддингу остальных примеров в батче. Пробовал добавлять наивные hard negatives, но заметной пользы они не дали.

  • Дистилляция распределения всех токенов из bert-base-multilingual-cased (через несколько эпох я отключил её, т.к. она начала мешать).

  • Приближение CLS-эмбеддингов (после линейной проекции) к эмбеддингам DeepPavlov/rubert-base-cased-sentence (усреднённым по токенам).

  • Приближение CLS-эмбеддингов (после другой линейной проекции) к CLS-эмбеддингам LaBSE.

  • Приближение CLS-эмбеддингов (после третьей проекции) к эмбеддингам LASER.

  • Приближение CLS-эмбеддингов (после ещё одной проекции) к эмбеддингам USE.

  • Расшифровка декодером от T5 предложения (на русском) из последней проекции CLS-эмбеддинга.

Скорее всего, из этих лоссов больше половины можно было безболезненно выкинуть, но ресурсов на ablation study я пока не нашёл. Обучал я это всё в течении нескольких дней на Colab, по пути нащупывая learning rate и другие параметры. В общем, не очень научно, но дешево и результативно. В качестве обучающей выборки я взял три параллельных корпуса англо-русских предложений: от Яндекс.Переводчика, OPUS-100 и Tatoeba, суммарно 2.5 млн коротких текстов. Весь процесс создания модели, включая некоторые неудачные эксперименты, содержится в блокноте. Сама модель, названная мною rubert-tiny (или просто Энкодечка), выложена в репозитории Huggingface.

И как этим пользоваться?

Если у вас есть Python и установлены пакет transformers и sentencepiece, скачать и запустить модель просто. Например, вот так вы можете получить 312-мерный CLS-эмбеддинг предложения.

Этот эмбеддинг вы можете использовать как признак для любой модели классификации или кластеризации текстов. Как (рекомендуемый) вариант, вы можете дообучить мою модель на собственной задаче. Про это будет отдельный пост, а пока отсылаю вас к обучающи материалам от Huggingface (раз, два).

Насколько быстр и мал мой Энкодечка? Я сравил его с другими BERT'ами, понимающими русский язык. Скорость указана в расчёте на одно предложение из Лейпцигского веб-корпуса русского языка.

Модель

Скорость (CPU)

Скорость (GPU)

Вес на диске

cointegrated/rubert-tiny

6 мс

3 мс

45 мб

bert-base-multilingual-cased

125 мс

8 мс

680 мб

DeepPavlov/rubert-base-cased-sentence

110 мс

8 мс

680 мб

sentence-transformers/LaBSE

120 мс

8 мс

1.8 гб

sberbank-ai/sbert_large_nlu_ru

420 мс

16 мс

1.6 гб

Все расчёты я выполнял на Colab (Intel(R) Xeon(R) CPU @ 2.00GHz и Tesla P100-PCIE c батчом размера 1 (если использовать крупные батчи, то ускорение на GPU ещё заметнее, т.к. с маленькой моделью можно собрать более большой батч).

Как видим, rubert-tiny на CPU работает раз в 20 быстрее своих ближайших соседей, и легко помещается на бюджетные хостинги типа Heroku (и даже, наверное, на мобильные устройства). Надеюсь, эта модель сделает предобученные нейросети для русского языка в целом более доступными для прикладных применений. Но надо ещё убедиться, что модель хоть чему-то научилась.

Оценка качества эмбеддингов

Рекомендованный и проверенный временем рецепт использования BERT дообучать его на конечную задачу. Но дообучение процесс небыстрый и наукоёмкий, а гипотезу об осмысленности выученных эмбеддингов хочется проверить побыстрее и попроще. Поэтому я не дообучаю модели, а использую их как готовые feature extractors, и обучаю поверх их эмбеддингов простые модели логистическую регрессию и KNN.

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

STS: бенчмарк по семантической близости предложений (переведённый с английского). Например, фразы "Кошка спит на фиолетовой простыне" и "Черно-белый кот спит на фиолетовом одеяле" из этого датасета оценены на 4 из 5 баллов сходства. Качество моделей на нём я мерял ранговой корреляций этих баллов с косинусной близостью эмбеддингов предложений. Для наилучшей модели, LaBSE, корреляция оказалась 77%, для моей 65%, на одном уровне с моделью от Сбера, которая в 40 раз больше моей.

Paraphraser: похожий бенчмарк по семантической близости, но с чисто русскими новостными заголовками и трёхбалльной шкалой. Эту задачу я решил так: обучил поверх косинусных близостей логистическую регрессию, и ей предсказывал, к какому из трёх классов пара заголовков относится. На этом бенчмарке моя модель выдала точность 43% и победила все остальные (впрочем, с небольшим отрывом).

XNLI: предсказание, следует ли одно предложение из другого. Например, из "это стреляющее пластиковое автоматическое оружие" не следует "это более надежно, чем металлическое оружие", но и не противоречит. Оценивал её я как предыдущую логрегом поверх косинусных близостей. Тут на первом месте оказалась модель от DeepPavlov (которая дообучалась ровно на этой задаче), а моя заняла второе.

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

OKMLCup: детекция токсичных комментариев из Одноклассников. Тут моя модель заняла четвёртое место по ROC AUC, обогнав только bert-base-cased-multilingual.

Inappropriateness: детекция сообщений, неприятных для собеседника или вредящих репутации. Тут моя модель оказалась на последнем месте, но таки набрала 68% AUC (у самой лучшей, Сберовской, вышло 79%).

Классификация интентов: накраудсоршенные обращения к голосовому помощнику, покрывающие 18 доменов и 68 интентов. Они собирались на английском языке, но я перевёл их на русский простой моделькой. Часть переводов получились странными, но для бенчмарка сойдёт. Оценивал я по точности логистической регрессии или KNN (что лучше). LaBSE набрала точность 75%, модель от Сбера 68%, от DeepPavlov 60%, моя 58%, мультиязычная 56%. Есть, куда расти.

Перенос классификации интентов с английского: та же классификация интентов, но обучающая выбора на английском языке, а тестовая на русском. Тут моя модель заняла второе место после LaBSE, а качество всех остальных сильно просело (что ожидаемо, ибо на параллельных корпусах они не обучались).

factRuEval-2016: задача распознавания классических именованных сущностей (адреса, организации, личности). Я обучал логистическую регрессию поверх эмбеддингов токенов, а качество мерял макро F1 скором (относительно токенов же, что не вполне корректно). Оказалось, что на таком NER моя модель работает откровенно плохо: она набрала скор 43%, остальные 67-69%.

RuDReC: распознавание медицинских именованных сущностей. Тут моя модель тоже проиграла остальным, но с меньшим отрывом: 58% против 62-67%.

Для всех задач, кроме NER, я пробовал для каждой модели два вида эмбеддингов: из CLS токена и усреднённые по всем токенам. Для меня было неожиданностью, что на многих задачах и моделях эмбеддинг СLS-токена оказывался даже лучше, чем средний (в целом, у него так себе репутация). Поэтому рекомендую не лениться и пробовать оба варианта. Код и результаты моей оценки можно взять из этого блокнота, но лучше подождать, пока я ещё причешу этот бенчмарк.

По итогам оценки оказалось, что модель LaBSE очень крутая: она заняла первое место на 6 из 10 задач. Поэтому я решил выложить LaBSE-en-ru, у которой я отрезал эмбеддинги всех 99 языков, кроме русского и английского. Модель похудела с 1.8 до 0.5 гигабайт, и, надеюсь, таким образом стала чуть более удобной для практического применения. Ну а rubert-tiny оказался по качеству в целом близок к моделям от DeepPavlov и Sber, будучи при этом на порядок меньше и быстрее.

Заключение

Я обещал сделать компактную модель для эмбеддингов русских предложений, и я это наконец сделал. Процесс дистилляции, скорее всего, я настроил неоптимально, и его ещё можно сильно улучшать, но уже сейчас маленькая модель на некоторых задачах приближается к уровню своих учителей и даже иногда обходит его. Так что если вам нужен маленький и быстрый BERT для русского языка, то пользуйтесь: https://huggingface.co/cointegrated/rubert-tiny.

Впереди работы много: с одной стороны, хочется обучить маленький BERT решать задачи из RussianSuperGLUE (и не только), с другой затащить в русский язык хорошие небольшие модели для контролируемой генерации текста (я уже начал делать это для T5). Посему лайкайте данный пост, подписывайтесь на мой канал про NLP, подкидывайте в комментариях и в личке интересные задачи, и, если у вас доведутся руки попробовать rubert-tiny, то обязательно оставляйте обратную связь!
Мне и самому интересно, что будет дальше.

Подробнее..

Перевод 5 разных библиотек Python, которые сэкономят ваше время

12.06.2021 18:20:44 | Автор: admin

В этой подборке, переводом которой мы решили поделиться к старту курса о машинном и глубоком обучении, по мнению автора, каждая библиотека заслуживает отдельной статьи. Всё начинается с самого начала: предлагается библиотека, которая сокращает шаблонный код импортирования; заканчивается статья пакетом удобной визуализации данных для исследовательского анализа. Автор также касается работы с картами Google, ускорения и упрощения работы с моделями ML и библиотеки, которая может повысить качество вашего проекта в области обработки естественного языка. Посвящённый подборке блокнот Jupyter вы найдёте в конце.


PyForest

Когда вы начинаете писать код для проекта, каков ваш первый шаг? Наверное, вы импортируете нужные библиотеки. Проблема в том, что заранее неизвестно, сколько библиотек нужно импортировать, пока они вам не понадобятся, то есть пока вы не получите ошибку.

Вот почему PyForest это одна из самых удобных библиотек, которые я знаю. С её помощью в ваш блокнот Jupyter можно импортировать более 40 популярнейших библиотек (Pandas, Matplotlib, Seaborn, Tensorflow, Sklearn, NLTK, XGBoost, Plotly, Keras, Numpy и другие) при помощи всего одной строки кода.

Выполните pip install pyforest. Для импорта библиотек в ваш блокнот введите команду from pyforest import *, и можно начинать. Чтобы узнать, какие библиотеки импортированы, выполните lazy_imports().

При этом с библиотеками удобно работать. Технически они импортируются только тогда, когда вы упоминаете их в коде. Если библиотека не упоминается, она не импортируется.

Emot

Эта библиотека может повысить качество вашего проекта по обработке естественного языка. Она преобразует эмотиконы в их описание. Представьте, например, что кто-то оставил в Твиттере сообщение I [здесь в оригинале эмодзи "красное сердце", новый редактор Хабра вырезает его] Python. Человек не написал слово люблю, вместо него вставив эмодзи. Если твит задействован в проекте, придётся удалить эмодзи, а значит, потерять часть информации.

Вот здесь и пригодится пакет emot, преобразующий эмодзи в слова. Для тех, кто не совсем понял, о чём речь, эмотиконы это способ выражения через символы. Например, :) означает улыбку, а :( выражает грусть. Как же работать с библиотекой?

Чтобы установить Emot, выполните команду pip install emot, а затем командой import emot импортируйте её в свой блокнот. Нужно решить, с чем вы хотите работать, то есть с эмотиконами или с эмодзи. В случае эмодзи код будет таким: emot.emoji(your_text). Посмотрим на emot в деле.

Выше видно предложение I [эмодзи "красное сердце"] Python, обёрнутое в метод Emot, чтобы разобраться со значениями. Код выводит словарь со значением, описанием и расположением символов. Как всегда, из словаря можно получить слайс и сосредоточиться на необходимой информации, например, если я напишу ans['mean'], вернётся только описание эмодзи.

Geemap

Говоря коротко, с её помощью можно интерактивно отображать данные Google Earth Engine. Наверное, вы знакомы с Google Earth Engine и всей его мощью, так почему не задействовать его в вашем проекте? За следующие несколько недель я хочу создать проект, раскрывающий всю функциональность пакета geemap, а ниже расскажу, как можно начать с ним работать.

Установите geemap командой pip install geemap из терминала, затем импортируйте в блокнот командой import geemap. Для демонстрации я создам интерактивную карту на основе folium:

import geemap.eefolium as geemapMap = geemap.Map(center=[40,-100], zoom=4)Map

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

Dabl

Позвольте мне рассказать об основах. Dabl создан, чтобы упростить работу с моделями ML для новичков. Чтобы установить её, выполните pip install dabl, импортируйте пакет командой import dabl и можно начинать. Выполните также строчку dabl.clean(data), чтобы получить информацию о признаках, например о том, есть ли какие-то бесполезные признаки. Она также показывает непрерывные, категориальные признаки и признаки с высокой кардинальностью.

Чтобы визуализировать конкретный признак, можно выполнить dabl.plot(data).

Наконец, одной строчкой кода вы можете создать несколько моделей вот так: dabl.AnyClassifier, или так: dabl.Simplefier(), как это делается в scikit-learn. Но на этом шаге придётся предпринять некоторые обычные шаги, такие как создание тренировочного и тестового набора данных, вызов, обучение модели и вывод её прогноза.

# Setting X and y variablesX, y = load_digits(return_X_y=True)# Splitting the dataset into train and test setsX_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)# Calling the modelsc = dabl.SimpleClassifier().fit(X_train, y_train)# Evaluating accuracy scoreprint(Accuracy score, sc.score(X_test, y_test))

Как видите, Dabl итеративно проходит через множество моделей, включая Dummy Classifier (фиктивный классификатор), GaussianNB (гауссовский наивный Байес), деревья решений различной глубины и логистическую регрессию. В конце библиотека показывает лучшую модель. Все модели отрабатывают примерно за 10 секунд. Круто, правда? Я решил протестировать последнюю модель при помощи scikit-learn, чтобы больше доверять результату:

Я получил точность 0,968 с обычным подходом к прогнозированию и 0,971 с помощью Dabl. Для меня это достаточно близко! Обратите внимание, что я не импортировал модель логистической регрессии из scikit-learn, поскольку это уже сделано через PyForest. Должен признаться, что предпочитаю LazyPredict, но Dabl стоит попробовать.

SweetViz

Это low-code библиотека, которая генерирует прекрасные визуализации, чтобы вывести ваш исследовательский анализ данных на новый уровень при помощи всего двух строк кода. Вывод библиотеки интерактивный файл HTML. Давайте посмотрим на неё в общем и целом. Установить её можно так: pip install sweetviz, а импортировать в блокнот строкой import sweetviz as sv. И вот пример кода:

my_report = sv.analyze(dataframe)my_report.show_html()

Вы видите это? Библиотека создаёт HTML-файл с исследовательским анализом данных на весь набор данных и разбивает его таким образом, что каждый признак вы можете проанализировать отдельно. Возможно также получить численные или категориальные ассоциации с другими признаками; малые, большие и часто встречающиеся значения. Также визуализация изменяется в зависимости от типа данных. При помощи SweetViz можно сделать так много, что я даже напишу о ней отдельный пост, а пока настоятельно рекомендую попробовать её.

Заключение

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

Этот материал не только даёт представление о полезных пакетах экосистемы Python, но и напоминает о широте и разнообразии проектов, в которых можно работать на этом языке. Python предельно лаконичен, он позволяет экономить время и в процессе написания кода, выражать идеи максимально быстро и эффективно, то есть беречь силы, чтобы придумывать новые подходы и решения задач, в том числе в области искусственного интеллекта, получить широкое и глубокое представление о котором вы можете на нашем курсе "Machine Learning и Deep Learning".

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Как построить AI друга. Расшифровка доклада

09.02.2021 10:17:03 | Автор: admin

Хабр, привет! Меня зовут Артем Родичев, я работаю Head of AI в компании Replika. Сегодня я расскажу как мы делаем AI-друга. Если вы смотрели фильм Her или последний Blade Runner, то уже можете представить что мы строим. На текущий момент, Реплика самый популярный англоговорящий чатбот, которому пользователи пишут более десяти миллионов сообщений в день. Под катом расшифровка доклада.

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

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

Немного цифр, которые отражают текущее состояние продукта:

У нас более 10 миллионов пользователей, Реплика это крупнейший англоговорящий чатбот, 80% наших пользователей из США.

Нам пишут более 100 миллионов сообщений в неделю, таким образом, мы high load сервис и постоянно инвестируем в масштабирование и оптимизацию нашей инфраструктуры.

В среднем каждый пользователь отправляет нам более 100 сообщений в день это много! Для сравнения, в среднем американцы пишут 94 сообщения в день, включая все социальные сети, мессенджеры и смски. Я часто шучу, что Реплика намного общительнее, чем ее создатели.

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

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

Quality

Как сказал Питер Друкер, Управлять можно только тем, что можно измерить, так что поговорим про самую важную часть нашего продукта качество диалога и как его измерить.

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

Агрегируя пользовательский фидбек, мы считаем несколько ключевых метрик. Одна из них доля положительных диалоговых сессий. Чтобы посчитать ее, мы суммируем все положительно оцененные сессии и делим их на общее количество диалоговых оценок. Аналогичным образом мы считаем долю отрицательных сессий. На текущий момент доля положительных сессий составляет более 80%, в то время как доля отрицательных меньше 4%. Остальные разговоры нейтральные.

Также пользователи могут проставить реакции отдельно на каждое сообщение. Ответы Реплики можно заапвоутить (то есть нажать палец вверх) если ответ понравился, или задаунвоутить (нажать палец вниз), если ответ показался нерелевантным, скучным или просто не имеет смысла. Таким образом мы можем получить метрику, показывающую долю апвоутов. На текущий момент эта метрика у нас выше 90%.

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

Мы хотим, чтобы у Реплики были интересные и увлекательные разговоры с пользователями. Скучные диалоги ведут к тому, что диалоговая сессия быстро заканчивается, а мы стараемся избежать таких диалогов. Мы отслеживаем 3 основные метрики отражающие вовлеченность пользователя:

  • Длина сессии, которая показывает среднее количество сообщений, которые пользователь отправляет за одну диалоговую сессию

  • Среднее количество сессий за день как часто пользователи разговаривают с Репликой в течение дня.

  • Последняя метрика это среднее количество сообщений, которые пользователи отправляют Реплике за день.

Теперь давайте посмотрим на высокоуровневую диаграмму архитектуры Реплики.

Когда пользователь отправляет нам сообщение, мы агрегируем такие данные, как профиль пользователя, текущий диалоговый контекст и само отправленное сообщение. Затем мы пропускаем эти данные через наш диалоговый движок. Он состоит из многих компонент, таких как различные NLP-классификаторы, генеративные и retrieval-based модели, скрипты, модели отвечающие за речь, компьютерное зрение и так далее. Далее мы рассмотрим несколько важных компонент нашего диалогового движка.

GPT-3

Начнем мы с одной из генеративных моделей, которую используем на продакшене. Это нашумевшая GPT-3

GPT-3 представляет из себя огромную модель-трансформер, который был натренирован компанией OpenAI. На самом деле OpenAI натренировала 8 моделей разных размеров. Самая маленькая модель содержит 125 миллионов параметров, а самая большая - 175 миллиардов. Учили сеть на топ-10 суперкомпьютере от Microsoft с тысячами GPU Nvidia V100. В качестве датасета использовали пол терабайта данных из интернета, включая Википедию, книги и скрауленные web-страницы.

GPT-3 является языковой моделью. Это означает, что в качестве входа она принимает текстовый префикс, а на выходе генерирует продолжение шаг за шагом. В примере выше в качестве префикса мы подаем предложение Recite the first law of robotics, что на русском означает Сформулируй первый закон робототехники. После того, как мы подали эту фразу на вход в обученную GPT-3, модель начинает генерировать корректный ответ слово за словом.

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

Реплика стала одними из первых партнеров OpenAI еще начале прошлого года. Объединив наши усилия, мы сфайнтюнили GPT-3 на нашем диалоговом датасете, провели десятки A/B-тестов на пользователях, cоптимизировали модель для использования под высокими нагрузками и выкатили лучшую модель на продакшн для миллионов наших пользователей.

Как я уже упоминал, наша ключевая метрика качества - это доля разговоров, улучшающих самочувствие пользователей. В начале 2020 года эта метрика была в районе 68%. Постоянно улучшая качество диалога, к маю мы дорастили ее до 74%, а после полноценного запуска GPT-3 на продакшн в июне она выросла до 78%. На текущий момент каждое пятое сообщение, которым отвечает Реплика, приходит из GPT-3, а доля положительных диалоговых сессий - больше 80%.

На этом слайде представлены диалоги между нашими пользователями и Репликой. Я взял эти переписки из наших публичных групп в Фейсбуке и Реддите, где пользователи выкладывают скриншоты своих разговоров. Ответы реплики находятся слева, а сообщения пользователей - справа. Все ответы Реплики здесь сгенерированы GPT-3.

На левом скриншоте пользователь просит Реплику генерировать слова, начинающиеся с указанной буквы. GPT-3 генерирует довольно интересные и эмпатичные ответы, используя эти буквы.

На скриншоте по центру GPT-3 демонстрирует способность хорошо запоминать контекст. В начале пользователь просит запомнить слово fish и затем в конце спрашивает, какое слово он попросил запомнить, на что Реплика генерирует правильный ответ.

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

Retrieval dialog model

Теперь давайте посмотрим на то, что из себя представляет модель, которая отвечает за большую часть ответов Реплики - retrieval диалоговая модель.

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

В этом примере для контекста Let's go to an early movie два ответа "Okay, which one do you want?" и "Sure, what time are you free?" являются релевантными, а остальные ответы плохо подходят под данный контекст. Задача заключается в том, чтобы наша модель давала высокие скоры релевантным ответам и низкие - не релевантным.

Для того чтобы эффективно решить такую задачу, мы используем Dual Encoder архитектуру, состоящую из двух энкодеров - один для диалоговых контекстов, другой - для ответов. У этих энкодеров одинаковая архитектура, но разные непошаренные веса. Энкодеры переводят как контекст, так и ответ в общее латентное пространство, что позволяет применить косинусную меру в качестве меры релевантности. При тренировке модели мы используем кросс-энтропию в качестве лосс-функции, что позволяет нам эффективно учитывать как положительные, так и негативные примеры.

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

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

Reranking model

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

Пайплайн работы выглядит следующим образом. Сначала мы получаем список топ-кандидатов из наших retrieval-моделей. Так же мы генерируем множество ответов нашими генеративными моделями, например, с помощью GPT-3. Дальше мы делаем пост-процессинг и фильтрацию этих ответов, выкидывая оскорбительные и не подходящие по другим эвристикам. В конце мы отдаем получившийся шорт-лист кандидатов в нашу реранкинг-модель, которая на выходе выдает финальный ответ, который с наибольшей вероятностью понравится пользователю.

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

После того, как мы собрали датасет состоящий из десятков миллионов примеров, мы тренируем модель на основе BERTa, которая учится предсказывать вероятность апвоута ответа при данном диалоговом контексте. После такой тренировки на выходе мы получаем модель, которая позволяет нам переранжировать ответы полученные из любых источников, будь то генеративные модели, retrieval-based и другие.

Vision

Теперь давайте поговорим про другие модальности. Следующая это Vision.

На этом слайде я хотел бы показать наш пайплайн обработки фотографий и картинок, которые нам присылают пользователи.

Для начала мы пытаемся распознать лица. На скриншоте слева пользователь прислал фотографию маленькой девочки. Реплика распознала, что это дочь пользователя. Более того, Реплика вспомнила ее имя и спросила, как поживает София.

Далее, если мы не распознали лиц, то мы делаем фолбек на модель, которая пытается распознать частотные объекты. Реплика понимает такие объекты как домашние животные, еда, растения, закаты, автомобили и так далее. На скриншоте по центру реплика распознала фотографию собаки, и спросила Как ты думаешь, в каком настроении сейчас твоя собака?.

Если же мы не распознали ни лиц, ни знакомых объектов, то мы подключаем нашу Vision Question Generation модель. Под капотом у нас хранится большая база фотографий, где для каждой фотки написано несколько отмодерированных релевантных вопросов. Когда пользователь присылает фото, мы высчитываем эмбеддинг этой фотки с помощью vision-модели и ищем в нашей базе такие фотки, которые максимально похожи на присланную. После этого, мы отвечаем вопросом, написанным для этой похожей фотографии, и довольно часто такие вопросы получаются релевантными. Например, на скриншоте справа можно увидеть, что Реплика задала довольно релевантный вопрос, который можно перевести как Почему она смотрит на пиццу, как будто она несчастлива?

Иногда что-то идет не по плану и Vision-модели ошибаются. Это может происходить по разным причинам, часто из-за того, что фото плохого качества. Например, на скриншоте слева пользователь прислал темную и размытую фотку Снуп Дога. Модель не распознала его и спросила, "что это за скейтборд?".

Бывает, что модель ошибается настолько сильно, что переходит на уровень шуток про мамку. Мы стараемся предотвратить и отфильтровать это настолько, насколько это возможно, но на наших масштабах в сотни миллионов сообщений иногда происходят факапы. На скрине по центру пользователь скинул фото лошади, на что сработала наша Question Generation модель и по какой-то причине спросила А не твоя ли это мама? Теперь это один из топ-постов в нашей группе на Reddit'e.

Еще один интересный факт: если вы даете пользователям возможность писать и спрашивать все что угодно, они будут спрашивать что угодно. Так, на скрине справа пользователь просит Реплику прислать дикпик. У нас есть nsfw-фильтрация и мы никогда не присылаем такой контент. В данном случае Реплика сделала, что ее просили и прислала фотку Дика Чейни - бывшего вице-президента США.

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

Для начала нам нужно определить, что пользователь начал говорить и мы должны начать его слушать. Для этого мы используем модель, которая называется Voice Activity Detection.

После того, как пользователь начал говорить, мы распознаем и переводим речь в текст, и отправляем его в наш диалоговый движок.

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

Все это должно происходить в real-time для того, чтобы получился классный пользовательский опыт. Какие-то вещи, такие как Voice Activity Detection, мы делаем прямо на девайсе. Какие-то, такие как распознавание и генерация речи, мы делаем на наших серверах, потому что на мобильных девайсах, особенно маломощных Андроидах, сложно добиться подходящего баланса качества/скорости.

3D & AR

Последняя модальность и наша недавняя фича - это 3D-модели и дополненная реальность.

Пользователь может выбрать, как будет выглядеть Реплика и кастомизировать ее. У нас есть 3D-модели разного пола и рас. Также можно настроить внешний вид, меняя прически, цвет волос, тон кожи и цвет глаз или же выбрать голос, которым Реплика будет с вами общаться.

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

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

Спасибо за внимание!

Подробнее..

Новые возможности для Python-разработчиков SmartApp Framework в open source

09.02.2021 10:17:03 | Автор: admin
Платформа SmartMarket позволяет разработчику с любым уровнем подготовки создавать мультимодальные приложения для виртуальных ассистентов Салют, даже без программирования. Конечно, если хочется сделать что-то красивое и сложное, без кода не обойтись. Чтобы облегчить жизнь разработчикам, мы делимся с ними нашими наработками в open source.На митапе разработчиков SmartMarket, прошедшем в декабре, мы рассказали о новом фреймворке.
Ниже вы найдете текстовую версию доклада и его видеозапись.


Друзья, привет! Меня зовут Кристина, я backend-разработчик SberDevices и тимлид сервиса управления диалогом, который используется для работы виртуальных ассистентов Салют. Расскажу вам сегодня о новом инструменте SmartMarket SmartApp Framework, который мы выложили в open source.

Итак, давайте посмотрим, что же это такое. Во-первых, это Python-фреймворк, который создан для того, чтобы вы могли разрабатывать backend для смартаповс поддержкой виртуальных ассистентов Салют. Мы активно используем этот фреймворк внутри Сбера, большое число наших собственных смартапов построено на нем. Это такие диалоговые приложения, как погода, текущее время, многие банковские навыки, это и диалоговая часть Canvas App, про который очень хорошо и подробно рассказал мой коллега Антон. Наш самый любимый и вкусный пример Canvas App это заказ попкорна, а также это app каталога.



C помощью фреймворка можно разрабатывать диалоговую часть Native App, как яркий пример это app видео. (О разных типах смартапов можно узнать здесь. Примеч. ред.)

Диалоговая механика


Давайте рассмотрим, что нам предоставляет фреймворк, на чем он основан. Одна из его основных особенностей в том, что диалоговая механика построена на механизме слотфиллинга. Что это такое? Приведу пример. Допустим, мы с вами хотим создать диалоговый навык P2P-перевода. Диалоговую часть можно схематично описать некоей формой, поля которой это те сущности, которые мы хотим узнать у пользователя. В частности, для случая P2P-перевода мы хотим у пользователя узнать его имя, сумму, которую он хочет перевести, и согласие на подтверждение операции.

Одно из основных свойств поля филлер. Филлер это сущность, извлекающая из запроса пользователя информацию, которую мы хотим узнать. Помимо этого у поля есть свойство question, которое позволяет задать вопрос пользователю.

Здесь есть несколько важных, на мой взгляд, моментов. Во-первых, диалог может строиться в произвольном порядке. Например, по шагам, когда пользователь сначала называет получателя, затем сумму. Также можно сказать в одном предложении: Переведи 100 рублей папе. Или просто: Переведи 100 рублей, и ассистент уточнит: Кому?. Таким образом, нет необходимости переспрашивать у пользователя информацию и учитывать всю вариативность в описании диалога.



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

Сам диалоговый сценарий мы можем описывать несколькими путями. Первый и самый простой декларативно с помощью нашего внутреннего языка DSL, который выглядит, как JSON. Здесь я привела пример того, как декларативно описать поле, которое извлекает имя пользователя и задает ему вопрос.



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

Предобработка текста


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

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



Работать с сырым JSON неудобно, поэтому для разработчиков внутри SmartApp Framework реализовано представление результатов предобработки в виде объектов классов, позволяющих удобно обращаться к сущностям предобработки. Благодаря этому филлер, который извлекает из предобработанного текста фамилию, имя, отчество пользователя, можно описать одной строкой на DSL или парой строк кода.

Запуск и тестирование смартапа


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



Если вы зарегистрированы в SmartApp Studio, вы умеете регистрировать свой вебхук и хотите попробовать запустить фреймворк, вам нужно сделать три простых шага:


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

  • третий командой run_app вы запускаете смартап и уже можете тестировать. Из коробки вам будет доступен тестовый сценарий, который знакомится с пользователем и показывает базовые умения ассистента.




На этом у меня все. Будем рады вашим отзывам, пулреквестам и даже issues. SmartApp Framework доступен по ссылке.

***
Приглашаем 11 февраля на Второй онлайн-митап разработчиков SmartMarket.
В этот раз поговорим об устройстве хорошего мультимодального приложения для виртуальных ассистентов Салют, возможностях ASR и TTS, а также о монетизации смартапов на платформе SmartMarket.
Узнать подробнее, что вас ждет на митапе, и зарегистрироваться на него можно здесь.
Подробнее..

Краткость сестра таланта Как сделать TransformerSummarizer на Trax

22.02.2021 10:14:04 | Автор: admin

В новой курсеровской специализации NLP от deeplearning.ai в качестве библиотеки глубокого обучения используется Trax. В последнем курсе подробно разбирается механизм внимания и его использование в архитектуре Transformer, в том числе в таких новеллах как BERT и T5. Имея некоторое количество свободного времени специализацию можно пройти за несколько недель, что я собственно и сделал, соблазнившись возможностью построить собственный трансформер. Очень хотелось сделать модель, которая может работать с текстами на русском языке.

Для эксперимента я выбрал саммаризатор, эта конструкция получает на вход статью и генерирует короткий текст с описанием сути. Summary может быть и просто заголовком. Попробую рассказать обо всём в деталях.

Trax полнофункциональная библиотека для глубокого обучения с фокусом на понятный код и быстрые вычисления. По синтаксису она в общем похожа на Keras, а модель на Trax можно сконвертировать в модель на Keras. Библиотека активно развивается и поддерживается командой Google Brain. Trax использует Tensorflow и является одной из библиотек в его экосистеме. Она работает на CPU, GPU и TPU, при этом используется одна и та же версия. Не буду говорить неправду, TPU я пока не попробовал.

Transformer - архитектура глубоких нейронных сетей, представленная в 2017 году исследователями из Google Brain. Transformer предназначен для работы с последовательностями, в том числе текстовыми, но в отличие от архитектур на рекуррентных сетях, не требует обрабатывать последовательность по порядку. Сильно упрощая можно сказать, что если из архитектуры Seq2Seq на LSTM с механизмом внимания оставить только механизм внимания и добавить нейронную сеть прямого распространения (Feed Forward), то он и получится. Подробнее про трансформеры с картинками здесь на английском, здесь на русском.

Данные

В качестве набора данных для эксперимента я решил использовать корпус новостей Lenta.Ru, свежую версию которого нашел на Kaggle. Корпус содержит более 800 тыс. новостных статей в формате (url, title, text, topic, tags, date). Если статья это text, то summary для моей модели title. Это законченное предложение, содержащее основную мысль новостной статьи. Конечно это не полное summary как, например, в англоязычном корпусе cnn_dailymail, но я подумал, что так даже интереснее.

Процесс подготовки данных представлен на схеме:

Для начала я отфильтровал аномально короткие и аномально длинные статьи. Затем выделил из набора тексты и заголовки, преобразовал всё к нижнему регистру, сохранил в виде списка кортежей и в виде полного текста. Список кортежей разбил на две части для обучения (train) и оценки (eval). Далее написал бесконечный генератор, который дойдя до конца списка, перемешивает его и начинает сначала. Неприятно же, когда генератор заканчивается где-то в середине эпохи. Это важно прежде всего для оценочного набора, я взял всего 5% от общего количества статей, примерно 36 тысяч пар.

На основе полного текста я обучил токенайзер, а в качестве токенов использовал части слов. Проблема токенизации или сегментации на целые слова заключается в том, что некоторые слова в тексте встречаются редко, возможно единственный раз, и таких слов очень много, а размер словаря конечен и хочется его сделать не очень большим, чтобы поместиться в память виртуальной машины. Приходится заменять некоторые слова именованными шаблонами, часто использовать заполнитель для слов, которых в словаре нет и даже использовать специальные техники вроде pointer-generator. А разбиение на подслова позволяет сделать токенайзер с небольшим по объему словарем, который еще и работает практически без потерь информации.

Для такой сегментации существует несколько сравнительно честных способов, познакомиться с ними можно например здесь. Я выбрал модель на основе Byte Pair Encoding (BPE), реализованную в библиотеке sentencepiece. BPE способ кодирования текста со сжатием. Для кодирования часто повторяющейся последовательности символов используется символ, которого нет в исходной последовательности. Всё тоже самое и при сегментации, только последовательность часто встречающихся символов становится новым токеном, и так пока не будет достигнут заданный размер словаря. Мой словарь содержит 16000 токенов.

Пример сегментированного текста

['ученые', 'придума', 'ли', 'новый', 'способ', 'взаимо', 'действия', 'с', 'граф', 'ен', 'ом', ',', 'который', 'позволяет', 'избавиться', 'от', '"', 'сли', 'па', 'ющихся', '"', 'ли', 'стов', '.', 'статья', 'ученых', 'появилась', 'в', 'журнале', 'ac', 's', 'n', 'an', 'o', ',', 'а', 'ее', 'крат', 'кое', 'из', 'ложение', 'приво', 'дится', 'на', 'сайте', 'северо', '-', 'запа', 'дного', 'университета', ',', 'сотрудники', 'которого', 'принимали', 'участие', 'в', 'работе', '.']

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

Обучается модель благодаря вот такой нехитрой конструкции:

import sentencepiece as spmspm.SentencePieceTrainer.train('--input=full_text.txt \                                --pad_id=0 --bos_id=-1 --eos_id=1 --unk_id=2 \                                --model_prefix=bpe --vocab_size=16000 --model_type=bpe')

Результат два файла: словарь для контроля и модель, которую можно загрузить в обертку токенайзера. Для выбранной мной модели статья и заголовок должны быть преобразованы в последовательности целых чисел и объединены с разделением служебными токенами EOS :1 и PAD :0 (конец последовательности и заполнитель).

После преобразования последовательность помещается в корзину фиксированной длинны. У меня их три: 256, 512 и 1024. Последовательности в корзине автоматически дополняются заполнителями до фиксированной длинны и собираются в пакеты (batches). Количество последовательностей в пакете зависит от корзины, соответственно 16, 8, 4.

Рефлексия по поводу последовательностей длиннее 512 токенов

Трудно представить, что 2000 символов могут дать что-то длиннее 512 токенов, но на всякий случай сделал три корзины. А длиннее 1024 не может быть в принципе из-за фильтра в пайплайне.

Сегментация и конкатенация выполняются в пайплайне trax:

input_pipeline = trax.data.Serial(    trax.data.Tokenize(vocab_type='sentencepiece',                       vocab_dir='/content/drive/MyDrive/',                       vocab_file='bpe.model'),    preprocessing,    trax.data.FilterByLength(1024))train_stream = input_pipeline(train_data_stream())eval_stream = input_pipeline(eval_data_stream())

preprocessing это моя функция конкатенации, генератор. Сортировка по корзинам и формирование пакетов осуществляется благодаря следующей конструкции:

boundaries =  [256, 512]batch_sizes = [16, 8, 4]train_batch_stream = trax.data.BucketByLength(    boundaries, batch_sizes)(train_stream)eval_batch_stream = trax.data.BucketByLength(    boundaries, batch_sizes)(eval_stream)

Модель

Transformer, работающий с двумя последовательностями, например при машинном переводе, включает два блока энкодер и декодер, но для саммаризации достаточно только декодера. Такая архитектура в общем реализует языковую модель, где вероятность следующего слова определяется по предыдущим. Еще её называют Decoder-only Transformer и она похожа на GPT (Generative Pre-trained Transformer). Разобраться в деталях архитектур можно здесь.

Для моего случая в библиотеке Trax есть отдельный класс моделей trax.models.transformer.TransformerLM(...), то есть создать модель можно одной строчкой кода. В упомянутой специализации модель строится from scratch. Я же выбрал нечто среднее построил модель из готовых блоков, используя примеры кода.

Схема модели показана на рисунке:

PositionlEncoder() это блок, обеспечивающий построение векторного пространства и кодирование позиции токена во входной последовательности. Код:

from trax import layers as tldef PositionalEncoder(vocab_size, d_model, dropout, max_len, mode):    return [         tl.Embedding(vocab_size, d_model),          tl.Dropout(rate=dropout, mode=mode),         tl.PositionalEncoding(max_len=max_len, mode=mode)] 

Аргументы:
vocab_size (int): размер словаря
d_model (int): количество признаков векторного пространства
dropout (float): степень использования dropout
max_len (int): максимальная длина последовательности для позиционного кодирования
mode (str): 'train' или 'eval' для dropout и поз. кодирования.

FeedForward формирует блок прямого распространения с выбранной функций активации:

def FeedForward(d_model, d_ff, dropout, mode, ff_activation):    return [         tl.LayerNorm(),         tl.Dense(d_ff),         ff_activation(),        tl.Dropout(rate=dropout, mode=mode),         tl.Dense(d_model),         tl.Dropout(rate=dropout, mode=mode)     ]

Аргументы:
d_model (int): количество признаков векторного пространства
d_ff (int): ширина блока или количество юнитов в выходном плотном слое
dropout (float): степень использования dropout
mode (str): 'train' или 'eval' чтобы не использовать dropout при оценке качества модели
ff_activation (function): функция активации, в моей модели ReLU

DecoderBlock(...) - это два блока с Residual-соединием. Вряд ли перевод остаточный точно отражает смысл, но это обходное соединение для борьбы с исчезающим градиентом в глубоких архитектурах.

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

def DecoderBlock(d_model, d_ff, n_heads, dropout, mode, ff_activation):            return [      tl.Residual(          tl.LayerNorm(),           tl.CausalAttention(d_model, n_heads=n_heads, dropout=dropout, mode=mode)         ),      tl.Residual(          FeedForward(d_model, d_ff, dropout, mode, ff_activation)        ),      ]

Из неизвестных аргументов только n_heads (int) количество головок внимания, надеюсь это удачный термин для attention heads. Каждая головка учится обращать внимание на что-то своё.

Собираю все части вместе и задаю параметры модели. У меня шесть декодеров, в каждом из которых по восемь головок внимания. Общее количество обучаемых параметров 37 412 480.

Из неизвестных мне уровней пожалуй только ShiftRight. Он сдвигает входную последовательность вправо, заполняя освободившееся место нулями, по умолчанию на одну позицию. Это нужно для teacher forcing, специальной техники, упрощающей обучение языковой модели, особенно на ранних этапах. Идея здесь в следующем: когда модель учится прогнозировать следующее слово по предыдущим, вместо прогноза модели, возможно неверного, в качестве этих предыдущих слов используются правильные ответы (ground truth). Коротко это можно описать формулой:
y(t) = x(t+1). Здесь подробное объяснение для RNN.

def SumTransformer(vocab_size=vocab_size,                  d_model=512,                  d_ff=2048,                  n_layers=6,                  n_heads=8,                  dropout=0.1,                  max_len=4096,                  mode='train',                  ff_activation=tl.Relu):    decoder_blocks = [DecoderBlock(d_model, d_ff, n_heads, dropout, mode,                       ff_activation) for _ in range(n_layers)]     return tl.Serial(        tl.ShiftRight(mode=mode),         PositionalEncoder(vocab_size, d_model, dropout, max_len, mode),        decoder_blocks,         tl.LayerNorm(),         tl.Dense(vocab_size),         tl.LogSoftmax()     )

Обучение

По моему опыту Google Colab не очень любит длительное использование своих GPU и не всегда их выделяет, особенно во второй половине дня. Поэтому я обучал модель отдельными эпохами по 20 000 шагов, где шаг соответствует одному пакету (batch). Получалось сделать 1-2 эпохи в день. 100 шагов это примерно минута, а эпоха около трех часов.

Первая эпоха показала, что модель учится только несколько тысяч шагов, дальше никаких улучшений не происходит. Оказалось, что я выбрал слишком большой шаг обучения (learning_rate). Для моей модели он должен быть 0.0002 первые несколько эпох, затем 0.0001 и 0.00005 в конце. Если бы я учил модель за один проход, то можно было бы использовать lr_schedules из trax.supervised. Там есть разные удобные варианты и с прогревом и с постепенным уменьшением шага.

В качестве метрик я использовал CrossEntropyLoss и Accuracy. За 12 эпох на оценочном наборе loss упал с 10 до 2, а доля правильных ответов возросла почти до 60%. Этого оказалось достаточно, чтобы генерировать почти приемлемые заголовки.

Цикл обучения выглядит следующим образом:

from trax.supervised import trainingdef training_loop(SumTransformer, train_gen, eval_gen, output_dir = "~/model"):    output_dir = os.path.expanduser(output_dir)    train_task = training.TrainTask(         labeled_data=train_gen,        loss_layer=tl.CrossEntropyLoss(),        optimizer=trax.optimizers.Adam(0.0001),        n_steps_per_checkpoint=100    )    eval_task = training.EvalTask(         labeled_data=eval_gen,         metrics=[tl.CrossEntropyLoss(), tl.Accuracy()]     )    loop = training.Loop(SumTransformer(),                         train_task,                         eval_tasks=[eval_task],                         output_dir=output_dir)        return loop

Аргументы:
SumTransformer (trax.layers.combinators.Serial): модель
train_gen (generator): поток данных для обучения
eval_gen (generator): поток данных для оценки качества.
output_dir (str): папка для файла модели, откуда её можно скопировать на Google Drive перед выключением виртуальной машины.

Дальше всё просто:

loop = training_loop(SumTransformer, train_batch_stream, eval_batch_stream)loop.run(20000)

и три часа ожидания...

Оценка результатов

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

Примеры из оценочного набора:
(Исходный текст сокращен)

Тест #1: швейцарская часовая компания audemars piguet представила новую модель из коллекции royal oak. как сообщает luxurylaunches, речь идет о часах с вечным календарем. официальная презентация пройдет в рамках международного салона высокого часового искусства sihh, который проходит в женеве...
Образец: дом audemars piguet оснастил часы вечным календарем
Модель: audemars piguet представила новую модель из коллекции royal oak

Тест #2: на ежегодном фестивале в городе грэхэмстаун, юар, фокусник случайно выстрелил в голову своему напарнику во время представления. об этом сообщает местная газета the daily dispatch. инцидент произошел 30 июня. брендон пил (brendon peel) и его ассистент ли лау (li lau) выполняли магический трюк перед многочисленной аудиторией, когда пил по неосторожности пустил в затылок напарника стрелу...
Образец: фокусник случайно подстрелил ассистента наглазах узрителей
Модель: на фестивале в грэлково напали с ножом
(И не в грэлково, и не напали, и не с ножом, но спасибо, что это было холодное оружие, а не пистолет)

Еще примеры

Тест #3: международный валютный фонд (мвф) в среду, 15 мая, утвердил выделение кипру кредита в размере 1,33 миллиарда долларов (миллиард евро). как сообщает agence france-presse, в качестве первого транша кипрское правительство получит 110,7 миллиона долларов. утвержденный 15 мая кредит является частью плана помощи...
Образец: мвф выделил кипру миллиард евро
Модель: мвф утвердил кредит на кипрский кредит

Тест #4: автопортрет энди уорхола, выполненный в 1965 году и ранее не выставлявшийся, продадут с аукциона, пишет the new york times. автопортрет более 40 лет хранила бывшая секретарша уорхола кэти нейсо (cathy naso), которая получила картину от художника в оплату ее работы. нейсо работала в студии уорхола...
Образец: неизвестный автопортрет энди уорхола выставят наторги
Модель: энди уорхола продадут с аукциона

Тест #5: sony решила выпустить файтинг, который станет "ответом на игру super smash bros" от nintendo, пишет vg24/7 со ссылкой на paul gale network и neogaf. в новом проекте, в настоящее время известном под названием title fight, герои из нескольких игр издательства сразятся между собой...
Образец: sony приписали разработку нового файтинга
Модель: sony выпустит файтинг от nintendo

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

Ссылки

  • Мой репозитарий с кодом эксперимента)

  • Репозитарий trax

  • Математика механизма внимания в знаменитой статье Attention Is All You Need. Кстати один из авторов статьи, Lukasz Kaiser, штатный исследователь Google Brain, является также автором и инструктором специализации.

Примечания

Я использовал trax 1.3.7, он инсталлируется через pip, но не работает под Windows. На форумах пишут что можно под WSL. А еще там нет beam_search, который есть в документации и который я очень хотел попробовать.

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

В упомянутой модели TransformerLM выход не нормализован (нет уровня softmax).

Подробнее..

Обработка и анализ текстов на Python и Spark NLP

08.04.2021 22:07:23 | Автор: admin

В наше время без анализа и обработки текстов,не обходится ни один проект, и так уж сложилось что Python обладает широким спектром библиотек и фреймворков для задач NLP. Задачи могут быть как тривиальные: анализ тональности(sentiment) текста, настроение, распознавание сущностей(NER) так и более интересные как боты, сравнение диалогов в саппорт-чатах - мониторить следует ли ваша тех.поддержка или сейлз текстовым скриптам, или постобработка текста после SpeechToText.

Для решения задач NLP имеется огромное количество инструментов. Вот короткий список таковых:

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

Для работы Spark NLP понадобится Java 8 - она нужна для фреймворка Apache Spark с помощью которого и работает Spark NLP. Для экспериментов на сервере или на локальной машине потребуется минимум 16Гб ОЗУ. Устанавливать лучше на каком нибудь дистрибутиве Linux (На macOS могут возникнуть трудности), лично я выбрал Ubuntu инстанс на AWS.

apt-get -qy install openjdk-8

Также нужно установить Python3 и сопутствующие библиотеки

apt-get -qy install build-essential python3 python3-pip python3-dev gnupg2

pip install nlu==1.1.3

pip install pyspark==2.4.7

pip install spark-nlp==2.7.4

Экспериментировать можно так же на colab. Работает Spark NLP по принципу конвейеров (pipeline), ваш текст проходит некоторое количество стадий которые вы описали в pipe-лайне, и каждая стадия производит описанные манипуляции, к примеру: пайплайн для получения именованных сущностей. Ниже на картинке схема наиболее часто встречающихся стадий которые будет проходить ваш входной текст, каждая стадия конвейера добавляет свою колонку с данными после ее выполнения.

Пример конвейера в Spark NLPПример конвейера в Spark NLP

Пример создания стадий для пайплайна. (весь код примера по ссылке на colab)

documentAssembler = DocumentAssembler() \    .setInputCol('text') \    .setOutputCol('document')tokenizer = Tokenizer() \    .setInputCols(['document']) \    .setOutputCol('token')embeddings = BertEmbeddings.pretrained(name='bert_base_cased', lang='en') \        .setInputCols(['document', 'token']) \        .setOutputCol('embeddings')ner_model = NerDLModel.pretrained('ner_dl_bert', 'en') \    .setInputCols(['document', 'token', 'embeddings']) \    .setOutputCol('ner')ner_converter = NerConverter() \    .setInputCols(['document', 'token', 'ner']) \    .setOutputCol('ner_chunk')nlp_pipeline = Pipeline(stages=[    documentAssembler,     tokenizer,    embeddings,    ner_model,    ner_converter])
  1. documentAssembler - создает аннотацию типаDocument,которая может использоваться аннотаторами в будущем

  2. tokenizer - разбивает текст и пунктуацию на массив строк

  3. embeddings -создает векторные представления для слов

  4. ner_model - распознаватель именованных сущностей. к примеру: October 28, 1955 = DATE

  5. ner_converter - добавляет колонку с отдельными распознанными сущностями October 28, 1955

И все конечно хорошо, но приходится как-то много кода писать - описывая стадии пайплайна и сам пайплайн, не говоря про подключение библиотек и инициализацию Spark NLP, поэтому разработчики SparkNLP (johnsnowlabs) сделали более высокоуровневую библиотеку или синтаксический сахар над SparkNLP - называйте как хотите, но когда мы попробуем повторить вышеприведенный пример:

import nlupipeline = nlu.load('ner')result = pipeline.predict(  text, output_level='document').to_dict(orient='records')

мы получим все те же самые NER, но написав на порядок меньше кода.

Хотелось бы еще отметить что оба варианта получения именованных сущностей, требуют некоторое время на инициализацию Apache Spark, предзагрузку моделей и установку связи интерпретатора Python c Spark через pyspark. Потому вам не особо захочется по 10-100 раз перезапускать скрипт с кодом выше, нужно предусмотреть предзагрузку и просто обрабатывать текст посредством вызова predict, в моем случае я сделал инициализацию нужных мне конвейеров во время инициализации Сelery воркеров.

# паттерн Реестрpipeline_registry = PipelineRegistry()def get_pipeline_registry():    pipeline_registry.register('sentiment', nlu.load('en.sentiment'))    pipeline_registry.register('ner', nlu.load('ner'))    pipeline_registry.register('stopwords', nlu.load('stopwords'))    pipeline_registry.register('stemmer', nlu.load('stemm'))    pipeline_registry.register('emotion', nlu.load('emotion'))    return pipeline_registry@worker_process_init.connectdef init_worker(**kwargs):    logging.info("Initializing pipeline_factory...")    get_pipeline_registry()

Таким образом можно выполнять NLP задачи без боли в мозгу и минимумом усилий.

Подробнее..
Категории: Python , Nlp , Spark , Nlu

Поиск по синонимам контролируем процесс или доверяемся нейросетям

28.01.2021 10:04:30 | Автор: admin
image

Первое что нужно сделать при разработке поисковых, диалоговых и прочих систем, основанных на natural language processing это научиться разбирать тексты пользовательских запросов и находить в них сущности рабочей модели. Задача нахождения стандартных сущностей (geo, date, money и т.д.) в целом уже решена, остается лишь выбрать подходящий NER компонент и воспользоваться его функционалом. Если же вам нужно найти элемент, характерный для вашей конкретной модели или вы нуждаетесь в улучшенном качестве поиска стандартного элемента, придется создать свой собственный NER компонент или обучить какой-то уже существующий под свои цели.

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

Если вы проектируете собственную систему, обучаете и настраиваете поисковые компоненты, например от Apache OpenNlp, Stanford NLP, Google Language API, Spacy или Apache NlpCraft для поиска собственных элементов, забот, разумеется, несколько больше, но и контроль над такой системой заметно выше.

Ниже поговорим о том, как нейронные сети используются при поиске сущностей в проекте Apache NlpCraft. Для начала вкратце опишем все возможности поиска в системе.

Поиск пользовательских сущностей в Apache NlpCraft

При построении систем на базе Apache NlpCraft вы можете использовать следующие возможности по поиску собственных элементов:
  • Встроенные компоненты поиска, основанные на конфигурации синонимов элементов. Пример описания элементов моделей, основанных на синонимах, Synonym DSL т.д. приведены в ознакомительной статье о проекте.
  • Использование любого из вышеупомянутых внешних компонентов, интеграция с ними уже предусмотрена, вы просто подключаете их в конфигурации.
  • Применение составных сущностей. Суть в возможности построения новых NER компонентов на основе уже существующих. Подробнее тут.
  • Самый низкоуровневый вариант программирование и подключение в систему своего собственного парсера. Данная задача сводится к имплементации интерфейса и добавлении его в систему через IoC. На входе реализуемого компонента есть все для написания логики поиска сущностей в запросе: сам запрос и его NLP представление, модель и все уже найденные в тексте запроса другими компонентами сущности. Эта имплементация место для подключения нейросетей, использования собственных алгоритмов, интеграции с любыми внешними системами и т.д., то есть точка полного контроля над поиском.

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

Ниже представлен фрагмент конфигурации модели умный дом (подробнее о макросах и synonym DSL можно прочесть по ссылке).
macros: - name: "<ACTION>"   macro: "{turn|switch|dial|control|let|set|get|put}" - name: "<ENTIRE_OPT>"   macro: "{entire|full|whole|total|*}" - name: "<LIGHT>"   macro: "{all|*} {it|them|light|illumination|lamp|lamplight}"...- id: "ls:on" description: "Light switch ON action." synonyms:   - "<ACTION> {on|up|*} <LIGHT> {on|up|*}"   - "<LIGHT> {on|up}"

Элемент ls:on описан очень компактно, при этом данное описание содержит в себе более 3000 синонимов. Вот их малая часть: set lamp, light on, control lamp, put them, switch it all Синонимы конфигурируются в весьма сжатом виде, при этом вполне читаемы.

Несколько замечаний:
  • Разумеется, при работе с поиском по синонимам учитываются начальные формы слов (леммы, стеммы), стоп-слова текста запроса, конфигурируется поддержка прерывистости многословных синонимов и т.д. и т.п.
  • Часть сгенерированных синонимов не будет иметь практического смысла, это вполне ожидаемая плата за компактность записи. Если использование памяти станет узким местом (тут речь должна идти о миллионах и более вариантов синонимов на сущность), стоит задуматься об оптимизации. Вы получите все необходимые warnings при старте системы.

Итак вы полностью управляете процессом поиска ваших элементов в тексте запросов, этот процесс является детерминированным, то есть в итоге отлаживаемым, контролируемым, и имеет возможности для последовательного улучшения. Теперь вам нужно лишь аккуратно составить достаточный список синонимов. Здесь вступает в игру человеческий фактор. На этапе старта проекта можно ограничиться лишь несколькими основными синонимами на элемент, достаточно добавить буквально одно-два слова, все будет работать, но в итоге конечно хочется поддержать максимально полный список синонимов для обеспечения наиболее качественного процесса распознавания.

Что может подсказать нам недостающие синонимы в конфигурации, ведь от ее полноты напрямую будет зависеть качество нашей системы?

Расширение списка синонимов

Первое очевидное направление это в ручном режиме отслеживать логи и анализировать неотвеченные вопросы.

Второе посмотреть в словаре синонимов, что может быть достаточно полезно для очевидных случаев. Один их самых известных словарей wordnet.

image

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

Помимо этого, разработчики Apache NlpCraft добавили в проект инструмент sugsyn, предлагающий, в процессе использования, дополнительные синонимы для элементов модели.

Компонент sugsyn работает стандартным образом через REST API, взаимодействуя с дополнительным сервером, поставляемым в бинарных релизах ContextWordServer.

Описание ContextWordServer

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

Изначально в качестве базовой модели был использован word2vec (skip-gram модель), позволяющий строить векторные представления слов (или эмбеддинги). Идея заключалась в том, чтобы посчитать эмбеддинги слов и основываясь на полученном векторном пространстве отобрать слова, находящиеся ближе всего к целевому.

В целом данный подход работал удовлетворительно, но контекст слов учитывался недостаточно хорошо, даже при больших значениях N для n-граммов. Тогда было предложено использование Bert для решения задачи masked language modeling (поиск наиболее подходящих слов, которые можно подставить вместо маски в предложение). В предложении, которое передавал пользователь, маскировалось целевое слово, и выдача Bert являлась ответом. Недостатком использования одного лишь Bert также было получение не совсем удовлетворительного списка слов, подходящих по контексту использования, но не являющихся близкими синонимами заданного.

Решением этой проблемы стало комбинирование двух методов. Теперь, выдача Bert отфильтровывается, выкидывая слова с векторным расстоянием до целевого слова выше определенного порога.

Далее, по результатам экспериментов, word2vec был заменен на fasttext (логический наследник word2vec), показывающий лучшие результаты. Была использована готовая модель, предобученная на статьях из Wikipedia, а модель Bert была заменена на улучшенную модель Roberta, смотри ссылку на предобученную модель.

Итоговый алгоритм не требует использования именно fasttext или Roberta в качестве составных частей. Обе подсистемы могут быть заменены альтернативными, способными решить схожие задачи. Кроме того, для лучшего результата предобученной модели могут быть fine-tuned, либо обучены с нуля.

Описание инструмента sugsyn

Работа с sugsyn, удобней всего осуществляется через CLI. Требуется скачать последнюю версию бинарного релиза, использование NlpCraft через maven central в данном случае будет недостаточно.

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

Пример. Пусть некий элемент какой-то модели сконфигурирован с помощью двух синонимов: ping и buzz (сокращенный вариант модели, взятой из примера с будильником), и пусть единственный интент модели содержит два примера: Ping me in 3 minutes и In an hour and 15mins, buzz me. Тогда sugsyn пошлет на ContextWordServer 4 запроса:
  • text=ping me in 3 minutes, index=0
  • text=buzz me in 3 minutes, index=0
  • text=in an hour and 15mins, ping me, index=6
  • text=in an hour and 15mins, buzz me, index=6

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

Использование инструмента sugsyn

Приступим к работе
  1. Запускам ContextWordServer, то есть сервер с готовыми, предобученными моделями.
    > cd ~/apache/incubator-nlpcraft/nlpcraft/src/main/python/ctxword
    > ./bin/start_server.sh

    Обратите внимание на то, что ContextWordServer должен быть предварительно проинсталирован и для успешной работы ему требуется python версий 3.6 3.8. См. install_dependencies.sh для linux и mac или мануал по установке для windows. Имейте в виду, что процесс инсталляции влечет за собой скачивание файлов моделей существенных размеров, будьте внимательны.
  2. Запускаем CLI, стартуем в нем server и probe с подготовленной моделью, подробности см. в разделе Quick Start. Для начальных экспериментов можно подготовить свою собственную модель или воспользоваться уже имеющимися в поставке примерами.
  3. Используем команду sugsyn, с одним обязательным параметром идентификатором модели, которая должна быть уже запущена в probe. Второй, необязательный параметр значение минимального коэффициента достоверности результата, поговорим о нем ниже.

image

Все описанное выше расписано шаг за шагом в мануале по ссылке.

Получение результатов для разных моделей

Начнем с примера по прогнозу погоды. Элемент wt:phen сконфигурирован с помощью множества синонимов среди которых rain, storm, sun, sunshine, cloud, dry и т.д., а элемент wt:fcast с помощью future, forecast, prognosis, prediction и т.д.

Вот часть ответа sugsyn на запрос со значением коэффициента minScore = 0.
> sugsyn --mdlId=nlpcraft.weather.ex --minScore=0

"wt:phen": [   { "score": 1.00000, "synonym": "flooding" },   ...   { "score": 0.55013,  "synonym": "freezing" },   ...   { "score": 0.09613, "synonym": "stop"},   { "score": 0.09520, "synonym": "crash" },   { "score": 0.09207, "synonym": "radar" },   ..."wt:fcast": [   { "score": 1.00000, "synonym": "outlook" },   { "score": 0.69549, "synonym": "news" },   { "score": 0.68009, "synonym": "trend" },   ...   { "score": 0.04898, "synonym": "where" },   { "score": 0.04848, "synonym": "classification" },   { "score": 0.04826, "synonym": "the" },   ...

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

Посмотрим, что будет предложено в качестве дополнительных синонимов для элементов примера умный дом. Для ls:loc, описывающего расположение элементов освещения (синонимы: kitchen, library, closet и т.д.), предложенные варианты с высокими значениями коэффициента, большим чем 0.5, тоже выглядят заслуживающими внимания:

"lc:loc": [   { "score": 1.00000, "synonym": "apartment" },   { "score": 0.96921, "synonym": "bed" },   { "score": 0.93816, "synonym": "area" },   { "score": 0.91766, "synonym": "hall" },   ...   { "score": 0.53512, "synonym": "attic" },   { "score": 0.51609, "synonym": "restroom" },   { "score": 0.51055, "synonym": "street" },   { "score": 0.48782, "synonym": "lounge" },   ...

Но около коэффициента 0.5 для нашей модели уже попадается откровенный мусор street.

Для элемента x:alarm модели будильник, с синонимами: ping, buzz, wake, call, hit и т.д. имеем следующий результат:

"x:alarm": [   { "score": 1.00000, "synonym": "ask" },   { "score": 0.94770, "synonym": "join" },   { "score": 0.73308, "synonym": "remember" },   ...   { "score": 0.51398, "synonym": "stop" },   { "score": 0.51369, "synonym": "kill" },   { "score": 0.50011, "synonym": "send" },   ... 

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

Для элемента x:time модели текущее время (1, 2), с синонимами типа what time, clock, date time, date and time и т.д. имеем следующий результат:
"x:time": [   { "score": 1.00000, "synonym": "night" },   { "score": 0.92325, "synonym": "year" },   { "score": 0.58671, "synonym": "place" },   { "score": 0.55458, "synonym": "month" },   { "score": 0.54937, "synonym": "events" },   { "score": 0.54466, "synonym": "pictures"},   ...    

Качество предложенных синонимов оказалось неудовлетворительным даже при высоких коэффициентах.

Оценка результатов

Перечислим факторы, от которых зависит качество синонимов, предлагаемых sugsyn для поиска в тексте элементов модели:
  • Количество синонимов, определенных пользователем в конфигурации элементов.
  • Количество и качество примеров запросов к интентам. Под качеством понимается естественность и распространенность добавленных примеров. Спросите гугл который час, и число полученных результатов будет примерно 2 630 000. Спросите час который, результатов будет примерно 136 000. Текст первого запроса более качественный, и для примера он подойдет лучше.
  • Самое главное и непредсказуемое качество зависит от типа самого элемента и типа его модели.

Иными словами, даже при прочих равных условиях, таких как достаточное количество заранее сконфигурированных синонимов и примеров использования, для некоторых видов сущностей нейронная сеть предложит более качественный набор синонимов, а для некоторых менее качественный, и это зависит от природы самих сущностей и моделей. То есть для достижения сопоставимого уровня качества поиска, выбирая из списка предлагаемых к использованию синонимов, для разных элементов и разных моделей мы должны использовать разные значения минимального коэффициента достоверности предлагаемых вариантов. В целом это вполне объяснимо, так как даже одни и те же слова в разных смысловых контекстах могут быть заменены несколько разными наборами слов заменителей. Предобученная модель, используемая по умолчанию с ContectWordServer может быть адаптирована для одних типов сущностей лучше чем для других, однако перенастройка модели может и не улучшить итоговый результат. Так как Apache NlpCraft это opensource решение, вы всегда можете изменить все настройки модели, как параметры, так и саму модель с учетом специфики вашей предметной области. К примеру, для специфичной, изолированной области, имеет смысл обучить модель bert с нуля, поскольку данных об этой области в стандартной модели может оказаться недостаточно.

При работе с sugsyn, помимо идентификатора модели, вы можете использовать всего один параметр minScore, просто ограничивая размер выборки синонимов, отсортированных в порядке снижения их качества. Подобное упрощение используется для облегчения работы специалистов, ответственных за расширение списка синонимов элементов настраиваемой модели. Обилие конфигурационных параметров, свойственное работе с нейронными сетями в данном случае способно лишь запутать пользователей системы.

Заключение

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

Перевод Как новая нейронная сеть Facebook решает дифференциальные уравнения

09.03.2021 20:23:18 | Автор: admin
Новая нейросеть может решить это уравнение за одну секундуНовая нейросеть может решить это уравнение за одну секунду

Два исследователя Facebook из Парижа создали для FB новую нейронную сеть, способную решать сложные математические уравнения, даже те, которые имеют дело с интегральным исчислением. Их работа описана в статье от 2 декабря, опубликованной в архиве arXiv (хранилище научных исследований под управлением Корнельского университета). Это ещё один большой шаг вперёд для нейронных сетей.


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

Ну, хорошо, возможно, в ближайшее время эта сеть не станет заменой Wolfram Alpha, но в Facebook действительно создали модель, которая может решать сложные математические задачи, а не выполнять простую старую арифметику, с которой модели ИИ обычно имели дело.

Эта работа огромный скачок в способности компьютеров понимать математическую логику. Исследование изложено в новой статье Deep Learning for Symbolic Mathematics (Глубокое обучение символьной математике), опубликованной в arXiv. двое парижских учёных, работающих на Facebook, Гийом Лампле и Франсуа Чартон, возглавили поход.

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

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

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

Благодаря уникальному подходу к разбору математической логики компьютеров Лампле и Чартон устранили эту проблему, позволив своей нейронной сети обрабатывать и решать задачи из математического анализа примерно за одну секунду. В своей статье они утверждают, что их нейронная сеть может превзойти другие коммерческие программные пакеты для алгебраических вычислений, такие как Matlab или Wolfram Mathematica, которые профессионалы отрасли обычно используют для вычислений с интегралами.

Зачем нужны дифференциальные уравнения?

На всякий случай, если вы давно изучали математику, напомним: дифференциальное уравнение это уравнение с одной или несколькими производными некоторой функции. Такие уравнения можно использовать, например, для расчёта скорости изменения быстро увеличивающейся популяции кроликов или, возможно, скорости снижения спроса на конкретную марку кроссовок.

Интегральное уравнение имеет дело с некоторой неизвестной функцией, стоящей под знаком интеграла. Если вы видели фильм Дрянные девчонки и помните, как во время соревнований по математике героиня Линдси Лохан кричала: Предела не существует!, вы на правильном пути.

Лампле и Чартон рассказывают, что решили сосредоточиться на дифференциальных уравнениях и интегралах по трём основным причинам:

  1. Это сложные задачи, которые часто преподают в университетах и которые трудно решить людям, (оставим машины в стороне).

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

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

Проблема с нейронными сетями

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

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

Имея это в виду, нейронные сети отлично подходят, чтобы распознавать изображения (пример квадрат на Facebook, который очерчивает лица друзей чтобы вы могли отметить их); они подходят, чтобы обыгрывать людей в стратегических играх, таких как шахматы или го, или даже помогать автономным автомобилям выявлять потенциальные дорожные опасности, а также прогнозировать поведение барьеров поблизости.

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

Разбиение выражений

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

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

Новый метод Лампле и Чартона включает разбиение сложных выражений на важнейшие составляющие. Затем учёные обучают нейронную сеть находить шаблоны математической логики, которые эквивалентны интегрированию и дифференцированию. Это позволяет программному обеспечению выполнять программу уникальным машинным способом. Затем учёные позволили нейронной сети найти новые выражения, которые не использовались при её обучении, и сравнили результаты с результатами от другого ПО, такого как Wolfram Mathematica и Matlab.

Для этого дуэт разложил уравнения на более мелкие части, представив уравнение в виде дерева. Каждый лист это число, константа или переменная, а каждый узел это символ оператора, такой как сложение, умножение, дифференцирование по переменной и так далее.

Например, выражение 2 + 3 x (5 + 2) можно представить следующим образом:

А выражение 3x2 + cos (2x) 1 разбивается на следующие части:

Обучение нейронной сети

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

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

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

На всех задачам мы видим, что наша модель значительно превосходит пакет Mathematica, написали они в своей статье. При интегрировании функций наша модель достигает точности почти в 100 %, тогда как Mathematica едва достигает 85 %.

За отведённые 30 секунд Matlab и Mathematica не нашли решения многих задач. Однако нейронной сети Facebook требуется всего лишь секунда, чтобы находить такие решения. Наш пример наверху одна из таких задач.

Хорошо, что теперь?

К сожалению, в статье Лампле или Чартон нет намёков на то, что в Facebook планируют делать с этой нейронной сетью. Однако, судя по тому, что в их интервью журналу Популярная механика постоянно упоминается обработка естественного языка, вполне возможно, что в Facebook работают над улучшением методов обработки лингвистической информации, которые могут использоваться для множества вещей. Или, может быть, эта гигантская социальная сеть просто хочет помочь вам с домашним заданием? Кто знает.

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

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


И, наконец, если вы хотели проверить свое знание математики, вот окончательный ответ, который вы должны были получить, решая уравнение по y в самом первом примере:

Не расстраивайтесь, если вы ошиблись, или если решение заняло у вас гораздо больше времени, чем 30 секунд, которые потребовались нейронной сети Facebook. В конце концов, вы всего лишь человек.

Чтобы быть крутым специалистом, важно не только разбираться в принципах работы классических моделей, но и понимать как это работает и тут понадобится математика. Получить нужные знания и навыки можно на курсе Математика для Data Science или на его расширенной версии Математика и Machine Learning для Data Science.

Узнать, как прокачаться в других специальностях или освоить востребованные профессии с нуля, можно здесь или по ссылкам ниже. Скидка только для хабравчан 50% по промокодуHABR.

Другие профессии и курсы
Подробнее..

Перевод Наглядно о том, почему трансформеры работают настолько хорошо

20.06.2021 18:15:44 | Автор: admin

Трансформеры за последние несколько лет штурмом захватили мир NLP, а сегодня они с успехом применяются в выходящих за рамки NLP приложениях. Они обладают такими возможностями благодаря модулю внимания, который схватывает отношения между всеми словами последовательностей. Но самый важный вопрос как именно трансформеры делают это? Попытаемся ответить и понять, почему трансформеры способны выполнять такие вычисления. Итак, цель статьи, чуть сокращённым переводом которой мы делимся к старту курса о машинном и глубоком обучении, разобраться не только с тем, как что-то работает, но и почему работает так. Чтобы понять, что движет трансформерами, мы должны сосредоточиться на модуле внимания. Начнём с входных данных и посмотрим, как они обрабатываются.


Как входная последовательность попадает в модуль внимания

Модуль внимания присутствует в каждом энкодере внутри стека каждого энкодера, а также внутри стека каждого декодера. Сначала внимательно посмотрим на энкодер.

Модуль внимания в энкодереМодуль внимания в энкодере

Для примера предположим, что мы работаем над задачей перевода с английского на испанский, где исходная последовательность слов The ball is blue, а целевая последовательность La bola es azul.

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

Внутри модуля внимания последовательность векторного представления проходит через три линейных слоя, создающих три отдельные матрицы запроса (Query), ключа (Key) и значения (Value). Именно эти три матрицы используются для вычисления оценки внимания [прим. перев. оценка определяет, сколько внимания нужно уделить другим частям входного предложения, когда мы кодируем слово в определённой позиции]. Важно помнить, что каждая "строка" этих матриц соответствует одному слову исходной последовательности.

Поток исходной последовательностиПоток исходной последовательности

Каждая входная строка это слово из последовательности

Чтобы понять, что происходит с модулем внимания, мы начнём с отдельных слов исходной последовательности, проследив их путь через трансформер. Если конкретнее, мы хотим сосредоточиться на происходящем внутри модуля внимания. Это поможет нам чётко увидеть, как каждое слово в исходной и целевой последовательностях взаимодействует с другими словами этих последовательностей.

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

Расположение каждого слова в исходной последовательностиРасположение каждого слова в исходной последовательности

Каждое слово проходит серию обучаемых преобразований (трансформаций)

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

Линейные веса и веса векторного представления обученыЛинейные веса и веса векторного представления обучены

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

Оценка внимания это скалярное произведение матрицы ключа и матрицы запроса слов

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

Многоголовое вниманиеМногоголовое вниманиеРасчёт оценки вниманияРасчёт оценки внимания

Как видно из формулы, первый шаг в рамках модуля внимания умножение матрицы, то есть скалярное произведение между матрицей Query (Q) и транспонированием матрицы ключа Key (K). Посмотрите, что происходит с каждым словом. Итог промежуточная матрица (назовём её факторной матрицей [матрицей множителей]), где каждая ячейка это результат матричного умножения двух слов.

Скалярное произведение матрицы запроса и матрицы ключаСкалярное произведение матрицы запроса и матрицы ключа

Например, каждый столбец в четвёртой строке соответствует скалярному произведению между четвёртым словом запроса и каждым ключевым словом.

Скалярное произведение между матрицами запроса и ключаСкалярное произведение между матрицами запроса и ключа

Оценка внимания скалярное произведение между запросом-ключом и значением слов

Следующим шагом является матричное умножение между этой промежуточной матрицей множителей и матрицей значений (V), чтобы получить оценку внимания, который выводится модулем внимания. Здесь мы можем видеть, что четвёртая строка соответствует четвёртой матрице слов запроса, умноженной на все остальные ключевые слова и значения.

Скалярное произведение между матрицами ключа запроса и значенияСкалярное произведение между матрицами ключа запроса и значения

Получается вектор оценки внимания (Z), который выводится модулем внимания. Выходной результат можно представить следующим образом: для каждого слова это закодированное значение каждого слова из матрицы Значение, взвешенное матрицей множителей. Матрица множителей представляет собой точечное произведение значения запроса для данного конкретного слова и значения ключа для всех слов.

Оценка внимания это взвешенная сумма значения словОценка внимания это взвешенная сумма значения слов

Какова роль слов запроса, ключа и значения?

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

Оценка внимания для слова blue обращает внимание на каждое словоОценка внимания для слова blue обращает внимание на каждое слово

Например, для предложения The ball is blue строка для слова blue будет содержать оценку внимания для слова blue с каждым вторым словом. Здесь blue это слово запроса, а другие слова ключ/значение. Выполняются и другие операции, такие как деление и softmax, но мы можем проигнорировать их в этой статье. Они просто изменяют числовые значения в матрицах, но не влияют на положение каждой строки слов в ней. Они также не предполагают никаких взаимодействий между словами.

Скалярное произведение сообщает нам о сходстве слов

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

Чтобы понять это, вспомните, что строки запроса, ключа и значения на самом деле являются векторами с размерностью векторного представления. Давайте посмотрим, как умножаются матрицы между этими векторами.

Каждая ячейка представляет собой скалярное произведение двух векторов словКаждая ячейка представляет собой скалярное произведение двух векторов слов

Для получения скалярного произведения двух векторов мы умножаем пары чисел, а затем суммируем их.

  • Если два парных числа (например, a и d выше) оба положительны или оба отрицательны, произведение положительно. Произведение увеличит итоговую сумму.

  • Если одно число положительное, а другое отрицательное, произведение будет отрицательным. Произведение уменьшит итоговую сумму.

  • Если произведение положительное, то, чем больше два числа, тем больше их вклад в окончательную сумму.

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

Как трансформер изучает релевантность между словами?

Скалярное произведение также применимо к оценке внимания. Если векторы для двух слов более выровнены, оценка внимания будет выше. Итак, какого поведения мы хотим от трансформера? Мы хотим, чтобы оценка внимания была высокой для двух релевантных друг другу слов в предложении. И мы хотим, чтобы оценка двух слов, не связанных друг с другом, была низкой.

Например, в предложении The black cat drank the milk слово milk очень релевантно к drank, возможно, немного менее релевантно для cat, и нерелевантно к black. Мы хотим, чтобы milk и drink давали высокую оценку внимания, чтобы milk и cat давали немного более низкую оценку, а для milk и black незначительную. Мы хотим, чтобы модель научилась воспроизводить этот результат. Чтобы достичь воспроизводимости, векторы слов milk и drank должны быть выровнены. Векторы milk и cat несколько разойдутся. А для milk и black они будут совершенно разными.

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

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

Следовательно, векторные представления слов milk и drank будут очень согласованными и обеспечат высокую оценку внимания. Они будут несколько отличаться для milk и cat, производить немного более низкую оценку и будут совершенно разными в случае milk и black: оценка внимания будет низкой вот лежащий в основе модуля внимания принцип.

Итак, как же работает трансформер?

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

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

Самовнимание энкодера в трансформере

Внимание используется в трансформере в трёх местах:

  • Самовнимание в энкодере исходная последовательность обращает внимание на себя.

  • Самовнимание в декодере целевая последовательность обращает внимание на себя.

  • Энкодер-декодер-внимание в декодере целевая последовательность обращает внимание на исходную последовательность.

Внимание в ТрансформереВнимание в Трансформере

В самовнимании энкодера мы вычисляем релевантность каждого слова в исходном предложении каждому другому слову в исходном предложении. Это происходит во всех энкодерах стека.

Декодер самовнимания в трансформере

Большая часть того, что мы только что видели в энкодере самовнимания, применима и к вниманию в декодере, но с некоторыми существенными отличиями.

Внимание в декодереВнимание в декодере

В декодере самовнимания мы вычисляем релевантность каждого слова в целевом предложении каждому другому слову в целевом предложении.

Самовнимание декодераСамовнимание декодера

Энкодер-декодер модуля внимания в трансформере

В энкодере-декодере запрос получается из целевого предложения, а ключ/значение из исходного предложения. Таким образом, он вычисляет релевантность каждого слова в целевом предложении каждому слову в исходном предложении.

Энкодер-декодер ВниманияЭнкодер-декодер Внимания

Заключение

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

Здесь мы видим, что за сложными идеями скрываются простые решения. Более того, есть ощутимая вероятность того, что вскоре понимание внутренних механизмов глубокого обучения станет второй грамотностью, как сегодня второй грамотностью стало знание ПК в целом и если вы хотите углубиться в область глубокого и машинного обучения, получить полное представление о современном ИИ, вы можете присмотреться к нашему курсу Machine Learning иDeep Learning, партнёром которого является компания NVIDIA.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Нейросетевой подход к моделированию карточных транзакций

19.04.2021 14:06:47 | Автор: admin

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

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

В рамках соревнования Альфа-Баттл 2.0 на boosters.pro участникам предлагалось решить задачу кредитного скоринга, используя только транзакционные данные клиента за предшествующий год. После соревнования была организована песочница, являющаяся копией соревнования, но без ограничения по времени и без денежных призов. Датасет соревнования можно использовать в научных публикациях, дипломных и курсовых работах.

Описание данных

Участникам соревнования был предоставлен датасет по 1.5 миллионам выдач кредитных продуктов. К каждому объекту выборки было подтянуто признаковое описание в виде истории клиентских транзакций глубиной в год. Дополнительно был представлен тип выданного продукта. Обучающая выборка состоит из выдач за период в N дней, тестовая выборка содержит выдачи за последующий период в K дней. Всего в датасете содержалось 450 миллионов транзакций, объемом порядка 6 гигабайт в формате parquet. Понимая, что такой объем данных может стать серьезным порогом для входа, мы разбили датасет на 120 файлов и реализовали методы пакетной предобработки данных, что позволило решать задачу соревнования с личного ноутбука.

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

Признаки карточных транзакций

название признака

описание

Кол-во уникальных значений

currency

Идентификатор валюты транзакции

11

operation_kind

Идентификатор типа транзакции

7

card_type

Уникальный идентификатор типа карты

175

operation_type

Идентификатор типа операции по пластиковой карте

22

operation_type_group

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

4

ecommerce_flag

Признак электронной коммерции

3

payment_system

Идентификатор типа платежной системы

7

income_flag

Признак списания/внесения денежных средств на карту

3

mcc

Уникальный идентификатор типа торговой точки

108

country

Идентификатор страны транзакции

24

city

Идентификатор города транзакции

163

mcc_category

Идентификатор категории магазина транзакции

28

day_of_week

День недели, когда транзакция была совершена

7

hour

Час, когда транзакция была совершена

24

days_before

Количество дней до даты выдачи кредита

23

weekofyear

Номер недели в году, когда транзакция была совершена

53

hour_diff

Количество часов с момента прошлой транзакции для данного клиента

10

amnt

Нормированная сумма транзакции. 0.0 - соответствует пропускам

inf

Целевой переменной в соревновании была бинарная величина, соответствующая флагу дефолта по кредитному продукту. Метрикой для оценки качества решений была выбрана AUC ROC.

Базовый подход к решению задачи

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

В случае категориальных признаков можно использовать счетчики вхождений каждого значения каждой категориальной переменной или пойти дальше и использовать вектора из матричных разложений или основанных на них методах: LDA, BigARTM. Последний из которых позволяет получить векторное представление сразу для всех категориальных признаков за счет поддержи мультимодальности. Признаки можно отобрать на основе важности, полученной популярным методом permutaion importance или менее популярным target permutation. С базовым подходом, приносящим 0.752 AUC ROC на public LB, можно ознакомиться на git.

Архитектура нейронной сети

Решать задачу классификации многомерных временных рядов можно методами, используемыми в классификации текстов, если мысленно заменить каждое слово текста набором категориальных признаков. В области обработки естественного языка принято ставить каждому слову в соответствие числовой вектор, размерности сильно меньше, чем размера словаря. Обычно вектора слов предобучают на огромном корпусе текстовых документов методами обучения без учителя: word2vec, FastText, BERT, GPT-3. Основной мотивацией предобучения являются огромное количество параметров, которое нужно выучить в виду большого размера словаря и обычно небольшого размеченного датасета для решения прикладной задачи. В данной задаче ситуация обратная: менее 200 уникальных значений для каждой категориальной переменной и большой размеченный датасет.

Резюмируя вышесказанное, векторного представление для каждого категориального признака можно получить, используя стандартный Embedding Layer, работающий по следующему алгоритму: в начале задается желаемый размер векторного представления, затем инициализируется LookUp матрица из некоторого распределения, далее ее веса учатся вместе с остальной сетью методом backpropagation. Размер эмбеддинга обычно выбирается равным или меньше половины числа уникальных значений категориального признака. Любой вещественный признак можно перевести в категориальный операцией бинаризации. Вектор для каждой транзакции получается из конкатенации эмбеддингов, соответствующих значениям ее категориальных признаков.

Далее последовательность векторов транзакций отправим в BiLSTM для моделирования временных зависимостей. Затем избавимся от пространственной размерности при помощи конкатенации GlobalMaxPool1D и GlobalAveragePool1D для последовательностей векторов из рекуррентного и эмбеддинг-слоев. В итоге, предварительно пропустив полученный вектор через промежуточный полносвязный слой, остается добавить полносвязный слой с sigmoid для решения задачи бинарной классификации.

Обучение нейронной сети

Рассмотрим архитектуру нейронной сети, предложенную участникам соревнования в качестве продвинутого бейзлайна. Она несущественно отличается от вышеописанной и содержит сотни тысяч обучаемых параметров. Тяжелые модели склонны переобучаться, запоминая обучающую выборку и показывая низкое качество на новых объектах. На курсах по машинному обучению учат использовать L1 и L2 регуляризацию в контексте логистической регрессии для борьбы с переобучением, будем использовать это знание и установим коэффициенты регуляризации для всех параметров нейронной сети. Не забудем использовать Dropout перед полносвязным слоем и SpatialDropout1D после эмбеддинг-слоя, также стоит помнить что всегда можно облегчить сеть, уменьшив размеры скрытых слоев.

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

Из графика видно, что распределение находится в семействе экспоненциальных, это приводит при стратегии паддинга до максимальной длины к увеличению объема входных данных в нейронную сеть на три порядка. Простым решением данной проблемы, ускоряющим обучение в 3 раза, является подход Sequence Bucketing, предлагающий нарезать датасет на группы-бакеты в зависимости от их длины и семплировать батчи из одного бакета. При обучении рекуррентных слоев рекомендуется задавать такой набор параметров сети, при котором поддерживается cudnn-реализация, ускоряющая обучение в этой задаче в пять раз.

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

  1. Cyclical Learning Rate благодаря стратегии изменения LR позволяет не переобучаться на первой эпохе за счет низкого базового LR и не застревать в локальных минимумах за счет пилообразной-затухающей стратегии. Гиперпараметры base_lr и max_lr можно задать при помощи алгоритма LRFinder. Дополнительно стоит обратить внимание на One Cycle Policy и Super-Convergence.

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

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

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

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

Продакшн

Транзакции в hadoop обновляются раз в день, поэтому online-обработка не требуется, в худшем случае нужно успевать прогонять pipeline за 24 часа. Pipeline реализован в виде DAG на airflow, состоящем из следующих этапов:

  1. Дозагрузка и агрегация новых данных на pyspasrk. В модели используется история транзакции по каждому пользователю за предыдущий год. Оптимальным сценарием является поддержание исторических предобработанных данных на момент предыдущего запуска модели с добавлением новых данных. На выходе: относительно-компактный parquet-датафрейм.

  2. Предсказание на python с использованием одного из фреймворка: tensorfow, pytorch. На выходе: tsv-таблица, содержащая id и множество полей с предсказаниями моделей.

  3. Выгрузка предсказаний в hadoop и на вход финальной модели.

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

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

Полезные ссылки и материалы

Подробнее..

Machine Learning news

22.02.2021 20:07:34 | Автор: admin

Дисклеймер:

Здесь я собираю новости абсолютно субъективно.

Часть новостей - новости только для меня и могли появиться довольно давно. Просто я заметил их только сейчас.

Я сопровождаю новость своим комментарием, в котором излагаю причину, почему эта новость интересна мне.

  1. Похоже, что Transformers from Hugginface

Подробнее..

Как мы ИИ учили новости понимать

04.02.2021 14:12:29 | Автор: admin

Все мы хотим быть в курсе происходящего поэтому часть своего времени тратим на чтение новостей, и сейчас все чаще новости приходят не из новостных сайтов или газет, а из каки-то телеграм-каналов. В итоге, через какое-то время, оказывается, что ты подписан на десяток(а может и на десятки каналов), которые постоянно что-то пишут как следствие, тратится либо огромное количество времени на то, чтобы "что-то не пропустить". Но если посмотреть большинство из них пишут примерно об одном, просто по-разному. Так и пришла идея научить ИИ отбирать новости, которые действительно являются главными. Конечно, есть разные ТОП-ы, вроде Яндекс.Новостей или что-то вроде итогов дня от какого-то уважаемого СМИ, но везде есть нюансики. В этой статье я постараюсь описать эти нюансики и что у нас получилось, а что нет.

Нюансики и источники

Вообще новости очень субъективная вещь, каждый сам выбирает источники кому верить, а кому нет, именно поэтому очень часто получается достаточно однобокая картинка мира, если читать какое-то одно издание там есть редакционная политика, которая чаще всего топит "за определенную повестку". Да, раньше был прекрасный проект Яндекс.Новости, который сейчас тоже превратился в инструмент, где транслируется определенный пул новостей и это не проблема конкретного прокта, скорее это проблема того пула источников с которым они работают. Сразу забегая вперед у нас была аналогичная проблема, когда мы отбирали источники себе.

Чтобы побороть эту пресловутую однобокость мы для себя поставили задачу следующим образом:

  • У нас в качестве источников будут новостные телеграмм-каналы, которые оперативно следят за происходящим

  • Мы постарались отобрать телеграмм каналы с разной повесткой, как раз для того, чтобы уйти от однобокого освещения каких-либо новостей(да-да, по большому счету это тоже можно назвать редакционной политикой на этапе отбора, но придумать более объективный способ у нас не получилось)

  • Все отобранные источники равны, это значит, что канал с 100 тыс. подписчиками и канал с 10 тыс. подписчиками(но при этом уважаемого издания) с точки зрения авторитетности равны. Ну не умеет какое-то гос. СМИ в СММ, ну не игнорировать же его сообщения

Таким образом у нас получилось собрать порядка 100 изданий, который мы "читаем" и пытаемся собрать актуальную новостную картину дня. При этом для начала мы прошлись по рейтингу медиалогии для того, чтобы отобрать топовые таким образом у нас есть и, например, новости из телеграм-канала Известий, GQ, личного канала Ксении Собчак и т.д. Конечно мы посмотрели топовые каналы с огромным количеством подписчиков - типа канала Ивлеевой, но, если честно какой-то полезной информации мы там не нашли.

Собираем свой ТОП

Есть миллион разных вариантов, как можно собирать ТОП, особенно когда ты большой поисковик с кучей ресурсов, кучей перекрёстных данных, индексами цитирования, данными по просмотрам и т.д. Но у нас, очевидно всего этого нет и надо было как-то выкручиваться. В итоге пришлось придумать свою методику отбора сообщений из того, что есть. А было не много просто текст, количество просмотров сейчас и потенциальное количество просмотров(из статистики по каналу в целом)... и вообщем-то все. И да, еще надо не ошибиться то, что ты уже запостил "отпостить" нельзя(конечно технически можно, но тогда надо опять же садить человека, который будет следить и в общем-то теряется весь смысл). Короче, если ты выбрал какую-то новость и запостил помимо всего прочего надо следить за сюжетом и новые сообщения, которые добавляются в сюжет уже постить не надо, даже если очень хочется.

Таким образом перед собой мы поставили себе такую задачу чтобы обрабатывать весь этот поток данных:

  • У нас должна быть методика отбора новостей из разных каналов по принципу "похожести контекста", чтобы понимать что разные источники пишут одну и туже новость

  • У нас должна быть возможность отбирать "сообщения", которые бы максимально коротко и емко описывали новость т.е. чтобы в одном сообщении было как можно меньше "воды" и максимальное количество фактов

  • Новость должна быть "интересной" и эмоциональной т.е. мы должны как-то определить, что аудитория интересуется этой темой сейчас

А с точки зрения того, что должна уметь наша нейрона, чтобы вот это все можно было делать без человеческого вмешательства:

  • определять похожесть текста(потом мы обошлись без нейронки)

  • понимать в каком тексте у нас больше всего фактов тут мы решаем классическую NLP задачу по поиску NER тексте чембольше этих самых сущностей, тем больше фактов

  • уметь как-то понимать "будет" ли эта новость интересна читателям или нет

Технически получилось достаточно простое решение у нас есть всего 3 компонента, которые все эти сложные вещи автоматизируют: парсим источники, обрабатываем сообщения и добавляем метаданные по которым можно собирать и ранжировать(этим и занимается нейронка), обратная интеграция постим в канал. Ну и все это упаковываем в оркестратор, чтобы у нас все интеграции обработки не отваливались, а если и отваливались - то данные не просаживались.

"Идеальная" нейросеть для NLP

Прочитав кучу статей про достижения трансформеров в решении задачи NLP, конечно же было решено сразу попробовать BERT, ведь с помощью одной обученной нейросети сразу решить все задачи. В интернете даже оказалось несколько обученных моделей на русском языке. Но не тут-то было. На самом деле мы попробовали несколько нейросетей и отказались от них по одной и тоже причине нейросеть, которая отлично себя показывает в MVP не всегда лучший вариант для работы в продуктиве:

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

  • Тяжелые нейронные сети, вроде BERT достаточно ресурсоемкие если нет проблем с ресурсами, конечно это все можно запросто отбалансировать и запускать в параллели, но зачем это делать, если можно ту же самую задачу почти с такими же результатами решить с меньшими затратами на ресурсы.

В итоге, наш MVP с BERT умер в первый же день, когда мы попробовали обрабатывать 20 каналов обученная сеть искала криво(надо было тюнить), отдельный сервак, выделенный под работу BERT - отставал от новостей, т.е. количество сообщений приходивших для обработки росло быстрее, чем нейросеть могла их обрабатывать. Конечно можно было купить еще ресурсов, отбалансировать, но в этот момент мы поняли, что придется все начинать с нуля искать другую нейросеть, собирать свои датасеты и обучать(ну или тюнить).

Что перебрали и не выбрали:

  • BERT ресурсоемкая, медленная, сложно добавлять новые сущности NER в существующую модель, нужно достаточно большое количество ресурсов для обучения

  • Natasha очень крутой проект, если вам надо решать задачи NLP для русского языка: быстрая, точная, простой API, но без возможности тюнить самостоятельно. Т.е. если вам хватает функциональности "из коробки" отличное решение

  • Stanza достаточно популярный пакет готовых инструментов для решения NLP задач, наличием готовых моделей, относительно быстрый и точный

Но в итоге мы становились на Spacy, который на наш взгляд дал все, что нужно было нам:

  • достаточную скорость

  • точность и гибкость обучения

  • решение сразу нескольких NLP задач, используя один пакет: NER, лематизацию, анализ тональности текста, анализ зависимостей

  • небольшое потребление ресурсов(по сравнению с той же BERT)

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

Тут нужно сделать небольшую ремарку, в процессе написания статьи обнаружилось, что есть проект: Natasha-spacy, в котором ребята решили большинство граблей и этой моделью можно будет пользоваться в большинстве проектов, где не нужно регулярно обновлять модель. Но даже в этом случае, если бы была такая модель на момент решения задачи в рамках нашего проекта - пару недель, а то и больше, мы бы точно сэкономили.

Собираем датасет

Ни для кого не секрет, что если есть готовая модель и готовый датасет то вопрос обучения, это вопрос времени и ресурсов. И если посмотреть на тот же самый английский язык там все хорошо: есть куча готовых моделей на все случаи жизни. Нет модели окей, есть датасет, дополняй его и обучай или тюнингуй модель для своей задачи. У нас же проблема усугублялась тем, что при решении задачи NER нам было недостаточно стандартных PER, LOC, ORG сущностей, нам важно было добавить еще выделение "денег" и "дат" в разных форматах и формах, т.к. это тоже касается важных фактах в новостях.

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

В зависимости от решаемой задачи можно выбирать один из них или миксовать как-то. Но у всех у них есть одна проблема - они не содержат NER. Конечно можно посадить большое количество людей и разметить нужные вам тексты на нужные вам термины в этих текстах. Но, только если у вас есть ресурсы и время на разметку. У нас такой роскоши не было, и надо было как-то выкручиваться. Поэтому после недолгих поисков был найден еще один прекрасный проект Открытый корпус, который как раз и занимается тем, что собирает и выделяет набор сущностей в тексте. Да, конечно у проекта своя методология выделения сущностей(не совместимы с CONLLU), свои форматы выгружаемых текстов для которых пришлось писать свой парсер для приведения этого корпуса к формату CONLLU. Но, основной плюс этого всего был в том, что весь этот корпус собран вручную.

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

Эмоциональная окраска

Что может быть проще, думали мы и к тому моменту нашли уже несколько датасетов в которых все тексты разделялись на "позитивный" и "негативный". Но когда мы открыли и посмотрели на эти датасеты - нас ждало очередное разочарование:

  • все они в большей степени подходили для оценки комментариев, например для оценки товара, сервиса или продукта

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

  • когда мы говорим про новости зачастую очень сложно понять - а позитивная это новость или негативная, посмотрите, например, на актуальный ТОП сегодняшних новостей и ради интереса попробуйте отнести их к той или иной категории, а потом спросите хотя бы у 3-х друзей как они их отнесут

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

Отказываться было нельзя, поэтому решили собрать свой собственный датасет из "открытых данных". К открытым данным мы отнесли публичные старицы в Facebook(например themeduza, forbesrussia) новостных изданий и, внезапно, новости на государственном сайте ria.ru. И это стало нашим спасением если внимательно посмотреть на любую публикацию - внизу есть важная и нужная нам информация о среднестатистической реакции читателей на эту новость. О чудо! У нас есть - заголовки, короткий текст и полный текст новости, а главное набор реакций большого количества разных людей, реагирующей на эту информацию. После небольшого усложнения нашей инфраструктуры и ~полутора месяцев скрупулёзного сбора данных у нас появился нужный нам датасет.

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

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

Перенос в продуктив

Тут наверное немного надо еще рассказать про инфраструктуру собственно сколько ресурсов нужно для того, чтобы это все работало и не проседало при той нагрузке, которая у нас есть. А есть у нас есть примерно 4-5 тыс сообщений, которые надо распарсить, прогнать через нейронную сеть, которая должна обогатить каждое сообщение метаданными, а потом еще выбрать какое-то сообщение в качестве "главного".

Среднее количество сообщений в деньСреднее количество сообщений в день

И вся эта инфраструктура крутится на 4 GB RAM, 2 vCPUs со средней нагрузкой 8% на CPU, короче все очень экономичненько. Да, много сил и времени заняла тонкая настройка airflow, но это дало свои плоды(для понимания - airflow "из коробки" перманентно нагружал инстанс 16 GB RAM, 4 vCPUs в среднем на 32%). Очевидно, что использование более тяжелых моделей нейросетей требовало бы еще большего количество ресурсов. А в нашем случае, когда у нас сообщения анализируются порционно и все это происходит в рамках DAG-ов, которые каждый раз загружают и выгружают соответствующие модели это бы порушило архитектуру в целом.

ИнфраструктураИнфраструктура

Наконец настал "день X", когда мы собрали и запустили все компоненты в продуктив. На тот момент у нас было:

  • Загруженная история из интересующих каналов за последние полгода(интеграционное решение построено таким образом, что при добавлении нового канала оно подгружает историю за последние полгода, можно и больше, но не интересно)

  • Обученная нейронная сеть, которая могла обогащать каждое из сообщений дополнительной информацией выявлять NER для каждой записи, оценивать сообщения по уровню "интересности" и эмоциональному окрасу новости, выглядит это примерно так(абсолютно рандомный пример если что)

  "source": {      "id": 1115468824,      "username": "lentadnya",      "title": "Лента дня",      "participants": 47148    },    "text": "Россия, матушка, забери Донбасс домой: Маргарита Симоньян попросила присоединить Донбасс к России. Тут лучше не пересказывать, просто послушайте",    "views": 405,    "link": "https://t.me/lentadnya/16263",    "interesting": 0.12,    "reaction": {      "enjoyment": 0.04400996118783951,      "sadness": 0.0019097710028290749,      "disgust": 0.8650462031364441,      "anger": 0.08112426102161407,      "fear": 0.00790974497795105    },    "entities": [      "Россия",      "Маргарита Симоньян",      "Донбасс",      "России"    ],    "tags": [      "россия",      "маргарита симоньян",      "донбасс",      "россия"    ]

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

Проблема 1: Эмоциональная окраска и "интересность" хорошо аранжировалась только для сообщений, который были примерно в том же временном периоде, что и данные для обучения. Это как бы очевидно, но в действительности уже через 3-4 недели, показатели начинают плыть. Т.е. с историей мы уже ничего не сделаем, т.к. у нас нет никакой выборки по истории на которой мы могли бы нормально обучить модель и прогнать историю, чтобы она была нормально отранжирована. Для новый данных на регулярной основе мы продолжаем собирать данные из публичных источников и поддерживать актуальность модели.

Проблема 2: Актуальность NER тут тоже происходит деградация модели. Конечно медленнее, чем для эмоций и интересов, но все равно точность просаживается. Пока эта проблема решается костылем. Костыль в нашем случае вылудит следующим образом - раз в неделю мы деваемы выборку по 100 рандомным сообщениям и вручную(да, к сожалению пока так) оцениваем на сколько точно происходит выявление NER и их лемматизация. Как только показатель падает меньше чем 85%. Происходит обучение модели на новых данных и снова контролируем этот показатель. Есть гипотеза обучить более тяжелую модель, например BERT в качестве "эталонной" и просто периодически сверяться на ней, но пока эта гипотеза только в проработке - если будет интересно, отдельно расскажем об этом как-нибудь.

Проблема 3: Дело в том, что в процессе анализа источников и выборки сообщения для формирования своего собственного рейтинга главных новостей, как я писал в начале, мы используем в том числе показатель, который показывает количество разных источников, которые написали сообщения в этом сюжете. Чтобы понять на сколько этот сюжет сейчас интересный. Ведь чем больше каналов пишут об этом - тем эта "новость" более важна и актуальная. Но мы столкнулись с тем, что в телеграмме есть "консорциумы", которые в определенный момент времени начинают форсить какое либо сообщение. Да, они не репостят одно и тоже сообщение(с этим было бы проще), но они в свойственной тому или иному каналу форсят одну и ту же мысль с указанием одинаковыми набором NER и эмоциональной окраской выводят "новость в топ". Не будем называть конкретные каналы, но в результате таких манипуляций внезапно мы сталкиваемся с картиной, которую мы видим, например в топе новостей у Яндекса. При этом "независимые" каналы, в случае какой-то важной новости реже "в унисон" как следствие. Новости "сеток" прорываются и в наш топ. Да, можно выкинуть соответствующие "сетки" и оставить только один канал из группы, но это противоречит концепции нашей внутренней цензуры, так что пока думаем как решить эту проблему. Если есть предложения пишите в комментарии.

Ну и кто дочитал до этого момента, я надеюсь будет интересно а как же выглядит наш топ: https://t.me/mygenda.

Ну и как говорят модные блоггеры: подписывайтесь, делитесь комментариями и задайте вопросы. Надеюсь это было интересно.

Подробнее..

Перевод Трансферное обучение с Т5

26.02.2021 08:22:57 | Автор: admin

За последние несколько лет трансферное обучение дало толчок новой волне state-of-the-art результатов в обработке естественного языка (NLP). Эффективность трансферного обучения заключается в предварительном обучении модели на большом доступном неразмеченном корпусе текстов для одной из задач самообучения (self-supervised learning): например, языкового моделирования или заполнения пропусков в тексте. Затем модель может быть дообучена на меньших наборах данных и зачастую показывает (значительно) лучшие результаты, чем в случае обучения на одних только размеченных данных. Об успехах трансферного обучения стало известно еще в 2018 году, когда были представлены такие модели, как GPT, ULMFiT, ELMo, BERT, а в 2019 году успешность такого подхода стала еще более очевидна с разработкой новых моделей вроде XLNet, RoBERTa, ALBERT, Reformer и MT-DNN. Скорость, с которой эта сфера развивается, не позволяет, однако, с уверенностью сказать, какие из разработок оказали наибольшее влияние и насколько эффективно их можно комбинировать.


В статье Изучение ограничений трансферного обучения в объединенном text-to-text Трансформере представлено масштабное эмпирическое исследование, направленное на определение наилучших методов трансферного обучения и применение полученных инсайтов для создания новой модели, т.н. T5 (Text-To-Text Transfer Transformer). Также был предложен новый открытый набор данных для обучения Colossal Clean Crawled Corpus (C4). Модель Т5, предобученная на корпусе С4, показывает наилучшие результаты во многих NLP бенчмарках, обладая, при этом, достаточной гибкостью для тонкой настройки под конкретные задачи обработки текстов. Для того, чтобы полученные результаты могли быть расширены и воспроизводимы, авторы представили код и предобученные модели, наряду с Colab-ноутбуком для знакомства с ними.


Общий Text-to-Text фреймворк


Наряду с Т5 авторы предлагают переосмыслить все NLP задачи и представить их в формате text-to-text, где вход и выход модели представляются текстовыми строками, в отличие от моделей типа BERT, подающие на выход или метку класса, или фрагмент входной последовательности. Предложенный же фреймворк позволяет использовать одну и ту же модель, функцию потерь и гиперпараметры для любой задачи NLP, включая машинный перевод, суммаризацию документов, вопросно-ответные системы, задачу классификации (например, анализ тональности). Модель Т5 можно использовать даже для задачи регрессии, обучив ее предсказывать строковое представление числа вместо самого числа.


image3


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


Большой набор данных для обучения (С4)


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


Для того, чтобы удовлетворить требованиям, описанным выше, был разработан Colossal Clean Crawled Corpus (C4) вычищенная версия Common Crawl, объем которой на два порядка превышает объем Википедии. Процесс очистки набора данных включал удаление дубликатов, неполных предложений, а также неприемлемых или мусорных текстов. Подобная фильтрация способствовала получению лучших результатов в прикладных задачах, в то время как большой объем позволил увеличить размер модели без риска переобучения. С4 доступен в TensorFlow Datasets.


Систематическое исследование методологии трансферного обучения


С помощью предложенного T5 text-to-text фреймворка и нового набора данных для предварительного обучения С4 авторы смогли исследовать довольно большое количество идей и методов, предложенных в сфере трансферного обучения в NLP в последние несколько лет. Все детали исследования можно найти в соответствующей статье, включая эксперименты с:


  • архитектурой, в ходе которых выяснилось, что модели с энкодером и декодером обычно превосходят языковые модели с одним декодером;
  • целями предварительного обучения, в ходе чего подтвердилось, что задачи типа заполни_пропуск (где модель обучается восстанавливать пропущенные слова во входном тексте) являются наилучшим решением и что самым важным фактором оказываются затраты на вычисление;
  • неразмеченными наборами данных, в ходе которых удалось показать, что обучение на данных определенной предметной области может оказаться полезным, но что предварительное обучение на небольших наборах данных может привести к переобучению;
  • стратегиями обучения, в ходе которых выяснилось, что многозадачное обучение может быть сравнимо с предварительным обучением и последующей тонкой настройкой, однако первое требует внимательного выбора частоты обучения модели на определенную задачу;
  • масштабами, в ходе которых сравнивались увеличение размера модели, времени обучения и числа моделей в ансамбле для определения наиболее эффективного использования имеющейся вычислительной мощности.

Инсайты + Масштаб = State-of-the-Art


Для нахождения существующих ограничений трансферного обучения в NLP авторы проделали финальный набор экспериментов, в которых они объединили все лучшие методы, определенные в их систематическом исследовании, и масштабировали их подход с помощью TPU-ускорителей Google Cloud. Крупнейшая модель имела 11 миллиардов параметров и достигла уровня state-of-the-art в рамках бенчмарков GLUE, SuperGLUE, SQuAD и CNN/Daily Mail. Особенно неожиданным результатом оказалось достижение околочеловеческого уровня в понимании естественного языка в рамках бенчмарка SuperGLUE, который разрабатывался намеренно сложным для моделей машинного обучения, но легким для человека.


Расширения


Т5 достаточно гибка в применении для решения различных задач помимо тех, что указаны в статье, и зачастую справляется с этим с большим успехом. Ниже рассматривается использование Т5 для двух новых задач: ответы на вопросы без предварительного обучения на специальном корпусе вопросов-ответов (Closed-book question answering) и генерация текста для заполнения пропусков переменной длины.


Ответы на общие вопросы


Один из вариантов использования text-to-text фреймворка заключается в понимании прочитанного текста: модели подается некоторый контекст и вопрос, ответ на который ей необходимо научиться находить в заданном контексте. Например, можно подать на вход модели статью из Википедии об урагане Конни вместе с вопросом: Когда произошел ураган Конни?. После чего модель будет учиться находить в статье нужную дату: 3 августа 1955 года. Этот подход помог получить наилучшие результаты на стендфордском вопросно-ответном датасете (SQuAD).


Как продемонстрировано в Colab-ноутбуке и сопровождающей статье, авторы обучают Т5 отвечать на тривиальные вопросы в более сложной реализации, когда у модели нет доступа к внешнему знанию. Другими словами, чтобы ответить на вопрос, Т5 может использовать только те знания, которые хранятся в параметрах, полученных во время предварительного обучения без учителя. Это можно рассматривать как ограниченный вариант общей вопросно-ответной системы.


image2


Во время предварительного обучения Т5 учится заполнять очищенные слоты текста (отмечено как ) в текстах корпуса С4. Для применения Т5 для задачи ответа на общие вопросы была проведена тонкая настройка модели для получения ответа без сопровождения дополнительного контекста или прочей информации. Это заставило Т5 отвечать на вопросы на основе тех знаний, которые она получила в ходе предварительного обучения.

Т5 на удивление хорошо справляется с этой задачей. Модель с 11 миллиардами параметров генерирует точный текст ответа в 50.1%, 37.4% и 34.5% случаев в заданиях TriviaQA, WebQuestions и Natural Questions соответственно. Для сравнения: команда разработчиков Т5 играла против модели в викторине Pub trivia challenge и проиграла! Попробуйте сами, нажав на анимацию ниже.


t5-trivia-lrg


Генерация текста для заполнения пропусков


Большие языковые модели вроде GPT-2 превосходно справляются с задачей генерации довольно реалистично выглядящих текстов, так как они обучены на предсказание следующего слова поданной на вход последовательности. Это дало толчок многочисленным необычным приложениям вроде Talk To Transformer или текстовой игры AI Dungeon. Задача предварительного обучения Т5 сводится скорее к задаче генерации текста для заполнения пропусков, когда модель должна предсказать пропущенное слово в специально испорченном фрагменте текста. Это, в свою очередь, является обобщением задачи продолжения последовательности, т.к. пропуски могут появиться также и в конце предложения.


Чтобы извлечь практическую пользу из подобного предварительного обучения, было разработано задание размерного заполнения пропусков (sized fill-in-the-blank), в котором модель должна подставить вместо пропуска заданное число слов. Например, если мы передадим модели на вход предложение I like to eat peanut butter and _4_ sandwiches, мы обучим ее вставлять в пропуск около 4 слов.


После тонкой настройки Т5 на выполнение этого задания с использованием корпуса С4 итоговый выход стал достаточно реалистичным. Особенно любопытно смотреть, как модель подгоняет свои предсказания к заданному числу пропущенных слов. Например, при подаче такого входного предложения: I love peanut butter and _N_ sandwiches, где N изменяемый параметр, выход модели будет следующим:


image


Заключение


Авторам не терпится посмотреть, как разработчики смогут использовать результаты их исследования, код и предобученные модели для быстрого старта своих собственных проектов. Изучите Colab-ноутбук, чтобы начать работать с моделью, и делитесь результатами в Twitter.


Авторы


Подробнее..
Категории: Машинное обучение , Nlp , T5

Перевод Тематическое исследование распознавания именованных сущностей в биомедицине

04.06.2021 18:05:38 | Автор: admin

Не так давно у автора этой статьи возник вопрос: может ли простой метод сопоставления строк в сочетании с некоторыми простыми оптимизациями конкурировать с моделью, обученной с учителем, в биомедицинской задаче распознавания именованных сущностей (NER)? Автор сравнил эти два метода между собой и предположил, что при правильном подходе даже простые модели могут конкурировать со сложными системами, а мы к старту курса "Machine Learning и Deep Learning" перевели его статью.


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

Что касается системы сопоставления строк, я использовал классификатор QuickUMLS. QuickUMLS [1] как система сопоставления строк принимает на вход строку (например, документ или реферат статьи, содержащий медицинские понятия) и выводит все промежутки документа, которые соответствуют понятиям унифицированного языка медицинских систем (UMLS). Затем эти понятия могут быть повторно использованы в других условиях или в качестве исходных данных для других систем машинного обучения. По этой причине QuickUMLS можно рассматривать как удобный инструмент предварительной обработки для получения релевантных понятий из клинических и биомедицинских текстов. Однако в этой статье мы сосредоточимся на использовании QuickUMLS в качестве классификатора на сложном наборе данных MedMentions [2].

Рисунок 1. Схематическое описание того, как работает QuickUMLS. Получив строку, базу данных UMLS, превращённую в БД simstring, модель возвращает оптимальные соответствия, идентификаторы понятий и семантические типыРисунок 1. Схематическое описание того, как работает QuickUMLS. Получив строку, базу данных UMLS, превращённую в БД simstring, модель возвращает оптимальные соответствия, идентификаторы понятий и семантические типы

Некоторые ключевые моменты, которые необходимо знать о биомедицинском NER

Прежде чем мы погрузимся в проблему, которую пытаемся решить, полезно описать некоторые особенности биомедицинского NER. В целом проблема NER заключается в поиске именованных сущностей (например, известных мест, лиц, организаций и т. д.) в тексте. Как вы, вероятно, догадываетесь, многие из этих сущностей можно найти через контекст. Например, в таком предложении, как "Сэм и Дрю пошли в Колизей", мы можем сделать вывод, что Колизей это место, потому что вы обычно ходите в какие-то места. Аналогично, мы можем предположить, что "Сэм" это имя собственное, потому что слова в позиции подлежащего "идти", которые не являются обычными словами, это обычно имена.

В отличие от этого биомедицинская NER заключается в поиске и однозначном определении интересующих биомедицинских терминов из текста, таких как заболевания, названия лекарств, а также общих терминов, таких как "больница.роддом" (hospital), "палата интенсивной терапии/подопечный отделения интенсивной терапии" или "алкоголь/спирт" (alcohol). Это важное различие, поскольку существует очень мало контекстуальной информации, определяющей, имеет ли данное слово медицинское значение. Чтобы привести немного нагруженный пример, рассмотрим слово "alcohol" в предложении "пациент выпил много alcohol" [для ясности того, что речь идёт о неоднозначности, оставлено оригинальное alcohol]. Тяжесть этого заключения зависит от того, относится ли оно к алкоголю, такому как пиво или вино, или к чистому спирту, такому как спирт для растирания. Для более полного обзора состояния дел в области биомедицинского NER см. эту запись в блоге моего коллеги из Slimmer AI, Сибрена Янсена.

Знать, какие понятия имеют медицинское значение, сложно без большого количества обучающих данных, которые, как правило, не всегда доступны. Поэтому многие системы используют унифицированный язык медицинских систем (UMLS), которая представляет собой большую онтологию, содержащую множество различных понятий вместе с их строковыми представлениями и другой информацией. Обратите внимание, что "понятие" здесь отличается от "строки", поскольку многие строки могут ссылаться на более чем одно понятие. Например, строка "alcohol" может относиться к спирту для растирания или к алкогольным напиткам.

В UMLS каждое понятие описывается уникальным идентификатором понятия (CUI), который является символическим идентификатором для любого данного уникального понятия, и семантическим типом (STY), который, в свою очередь, является идентификатором семейства, группирующим понятия с похожими характеристиками. Одной из причин, по которой UMLS является полезным, но в то же время сложным для работы, его огромный размер. Версия UMLS 2020AB, которую мы будем использовать в дальнейшем, насчитывает более 3 миллионов уникальных английских понятий. Маловероятно, что значительная часть этих понятий появится даже в больших аннотированных наборах данных.

Работа с набором данных MedMentions

Одним из таких наборов данных является MedMentions. Он состоит из 4 392 статей (заголовки и рефераты), опубликованных в Pubmed за 2016 год; аннотировано 352 K понятий (идентификаторов CUI) и семантических типов из UMLS. В документах имеется около 34 тысяч аннотированных уникальных понятий это около 1 % от общего числа понятий в UMLS. Факт показывает, что аннотирование упоминаний в UMLS является сложной задачей, которую не всегда можно решить с помощью машинного обучения с учителем.

Особый интерес в этом отношении представляет то, что корпус MedMentions включает в тестовое множество CUI, которые не встречаются в обучающем наборе. В целом, однако, эта задача всё ещё рассматривается как задача машинного обучения с учителем и с использованием семантических типов понятий UMLS в качестве меток. Поскольку UMLS имеет 127 семантических типов, это всё равно приводит к большому пространству меток. У набора данных MedMentions тоже есть уменьшенная версия st21pv, который состоит из тех же документов, что и обычный набор, но в нём аннотирован только 21 наиболее часто встречающийся семантический тип.

Полумарковская базовая модель получает около 45,3 по F-мере на уровне сущностей [2]. Другие подходы, включая BlueBERT [3] и BioBERT [4], были протестированы и улучшили оценку до 56,3 балла, используя точное соответствие на уровне сущностей [5]. Обратите внимание, что все эти подходы являются контролируемыми и, следовательно, полагаются на определённое совпадение между обучающим и тестовым множеством в плане понятий. Если понятие или метка никогда не встречалась в процессе обучения, в подходе машинного обучения с учителем будет сложно её правильно классифицировать. Далее в качестве меток мы будем использовать семантические типы из набора данных MedMentions.

QuickUMLS: без учителя и на основе знаний

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

  1. Качество модели ограничено качеством базы знаний. Модель не может предсказать то, чего нет в базе знаний.

  2. Модель может обобщать не только аннотированные данные. Модель, которая обучалась с учителем и во время обучения не видела конкретной метки, в целом не может точно предсказать эти вещи. Исключение из правила методы обучения zero-shot.

Zero-shot learning (ZSL) это постановка задачи в машинном обучении, когда во время тестирования алгориттм наблюдает выборки из классов, которые не наблюдались во время обучения, и должен спрогнозировать, к какому классу они принадлежат.

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

Архитектура модели QuickUMLS

QuickUMLS как модель проста. Сначала она анализирует текст с помощью парсера spacy. Затем выбирает словесные n-граммы, то есть последовательности слов, на основе цитат и описаний цитат, а также списков стоп-слов. Это означает, что модель отбрасывает определённые словесные n-граммы, если они содержат нежелательные токены и знаки препинания. Подробные сведения об этих правилах можно найти в оригинальной статье [1]. После выбора кандидатов вся база данных UMLS запрашивается, чтобы найти понятия, частично соответствующие словам n-грамм. Поскольку точное сопоставление в такой огромной базе данных неэффективно и сложно, авторы выполняют приблизительное сопоставление строк с помощью simstring [6]. При задании текста QuickUMLS, таким образом, возвращает список понятий в UMLS вместе с их сходством со строкой запроса и другой связанной информацией. Например, текст У пациента было кровоизлияние, используя (по умолчанию) порог сходства строк 0,7, возвращает следующих кандидатов:

Для слова patient:

{term: Inpatient, cui: C1548438, similarity: 0.71, semtypes: {T078}, preferred: 1},{term: Inpatient, cui: C1549404, similarity: 0.71, semtypes: {T078}, preferred: 1},{term: Inpatient, cui: C1555324, similarity: 0.71, semtypes: {T058}, preferred: 1},{term: *^patient, cui: C0030705, similarity: 0.71, semtypes: {T101}, preferred: 1},{term: patient, cui: C0030705, similarity: 1.0, semtypes: {T101}, preferred: 0},{term: inpatient, cui: C0021562, similarity: 0.71, semtypes: {T101}, preferred: 0}

Для слова hemmorhage:

{term: No hemorrhage, cui: C1861265, similarity: 0.72, semtypes: {T033}, preferred: 1},{term: hemorrhagin, cui: C0121419, similarity: 0.7, semtypes: {T116, T126}, preferred: 1},{term: hemorrhagic, cui: C0333275, similarity: 0.7, semtypes: {T080}, preferred: 1},{term: hemorrhage, cui: C0019080, similarity: 1.0, semtypes: {T046}, preferred: 0},{term: GI hemorrhage, cui: C0017181, similarity: 0.72, semtypes: {T046}, preferred: 0},{term: Hemorrhages, cui: C0019080, similarity: 0.7, semtypes: {T046}, preferred: 0}

Как вы можете видеть, слово patient имеет три соответствия с корректным семантическим типом (T101) и два соответствия с корректным понятием (C0030705). Слово кровоизлияние также имеет лишние совпадения, включая понятие "No hemmorhage". Тем не менее кандидат с самым высоким рейтингом, если исходить из сходства, является правильным в обоих случаях.

В приложении QuickUMLS по умолчанию мы сохраняем только предпочтительные термины, то есть термины, для которых предпочтительным является 1, а затем сортируем по сходству. После мы берём семантический тип (семтип) кандидата с самым высоким рейтингом в качестве прогноза мы называем это базовой моделью (baseline model). Мы использовали seqeval со строгой парадигмой соответствия, которая сопоставима с предыдущей работой [5].

    BERT  QUMLS  P   .53    .27  R   .58    .36  F   .56    .31 Таблица 1  производительность базовой модели

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

Улучшение QuickUMLS с помощью некоторых простых оптимизаций

Есть несколько способов улучшить QuickUMLS помимо его первоначальной производительности. Во-первых, отметим, что стандартный синтаксический анализатор, используемый QuickUMLS, по умолчанию является моделью spacy, т. е. en_core_web_sm. Учитывая, что мы имеем дело с биомедицинским текстом, нам лучше применить модель биомедицинского языка. В нашем случае мы заменили spacy на scispacy [7], en_core_sci_sm. Это уже немного повышает производительность без каких-либо затрат.

    BERT  QUMLS  + Spacy  P   .53    .27      .29  R   .58    .36      .37  F   .56    .31      .32 Таблица 2  Замена на scispacy

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

Оптимизация порога QuickUMLS

Настройки по умолчанию для QuickUMLS включают пороговое значение 0,7 и набор метрик. Метрика определяет, как подсчитывается сходство строк, и может быть установлена в Jaccard, cosine, overlap и dice. Мы выполняем поиск по сетке, по метрике и различным пороговым значениям. Наилучшими результатами оказались пороговые значения 0,99, а это означает, что мы выполняем точные совпадения только с помощью SimString и метрики Jaccard, которая превосходит все другие варианты с точки зрения скорости и оценки. Как видите, мы всё ближе и ближе подходим к производительности BERT.

    BERT  QUMLS  + Spacy  + Grid  P   .53    .27      .29     .37  R   .58    .36      .37     .37  F   .56    .31      .32     .37 Таблица 3  Поиск по сетке параметров

Преимущество добавления априорной вероятности

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

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

    BERT  QUMLS  + Spacy  + Grid  + Priors  P   .53    .27      .29     .37       .39  R   .58    .36      .37     .37       .39  F   .56    .31      .32     .37       .39 Таблица 4  Добавление приоров

К сожалению, это всё, что мы могли бы получить, используя простую систему в QuickUMLS. Учитывая, что мы в конечном счёте использовали порог 0,99, это означает, что мы вообще не используем функциональность приблизительного сопоставления QuickUMLS. Удаление приблизительного сопоставления также значительно ускорит работу всей системы, так как большая часть времени алгоритма теперь тратится на сопоставление в QuickUMLS.

Глубокое погружение в анализ ошибок: соответствовало ли наше решение поставленной задаче?

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

Это показывает, что ошибки, которые делает QuickUMLS, не относятся к одной конкретной категории. Она извлекает слишком много элементов, но, когда она делает это, то также часто присваивает им неправильную метку. Это показывает, что QuickUMLS можно использовать в качестве системы предварительного извлечения, после чего для назначения правильной метки можно применить систему устранения неоднозначности.

Заключение

Как вы можете видеть из результатов, готовая система извлечения терминологии может быть использована в качестве эффективной системы NER без обучения. Получение обучающих данных для конкретных случаев применения часто может быть трудоёмким, снижающим скорость R&D процессом. Построенный нами классификатор QuickUMLS показывает, что мы можем многого добиться с очень небольшим количеством обучающих примеров. И, будучи разумными в том, как использовать ресурсы, мы в процессе исследований и разработок для биомедицинских исследований сэкономили много времени. Модифицированный классификатор QuickUMLS можно опробовать здесь, на github. Преимущество подхода может означать, что мы нашли решение, достаточно надёжное для достижения конечного результата, простое в разработке и тестировании, а также достаточно небольшое, чтобы легко внедрить его в разработку продукта.

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

Вместе с тем одни из самых успешных моделей так или иначе комбинируют подходы из разных областей ИИ, например мы писали о визуализации пения птиц, чтобы ИИ работал со звуком так же, как работает с изображениями и если вам интересна не только обработка естественного языка, но и эксперименты с ИИ в целом, вы можете обратить внимание на наш курс "Machine Learning и Deep Learning", партнёром которого является компания NVIDIA.

Ссылки

[1]L. Soldaini, and N. Goharian. Quickumls: a fast, unsupervised approach for medical concept extraction, (2016), MedIR workshop, SIGIR

[2]S. Mohan, and D. Li, Medmentions: a large biomedical corpus annotated with UMLS concepts, (2019), arXiv preprint arXiv:1902.09476

[3]Y. Peng, Q. Chen, and Z. Lu, An empirical study of multi-task learning on BERT for biomedical text mining, (2020), arXiv preprint arXiv:2005.02799

[4]J. Lee, W. Yoon, S. Kim, D. Kim, S. Kim, C.H. So, and J. Kang, BioBERT: a pre-trained biomedical language representation model for biomedical text mining, (2020), Bioinformatics, 36(4)

[5]K.C. Fraser, I. Nejadgholi, B. De Bruijn, M. Li, A. LaPlante and K.Z.E. Abidine, Extracting UMLS concepts from medical text using general and domain-specific deep learning models, (2019), arXiv preprint arXiv:1910.01274.

[6]N. Okazaki, and J.I. Tsujii, Simple and efficient algorithm for approximate dictionary matching, (2010, August), In Proceedings of the 23rd International Conference on Computational Linguistics (Coling 2010)

[7]M. Neumann, D. King, I. Beltagy, and W. Ammar, Scispacy: Fast and robust models for biomedical natural language processing, (2019),arXiv preprint arXiv:1902.07669.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Перевод Учимся понимать таблицы на меньшем объеме данных

21.06.2021 14:12:41 | Автор: admin

Задача распознавания семантического следования (textual entailment), или импликации (natural language inference), в текстах на естественном языке состоит в определении того, может ли часть текста (посылка, антецедент) подразумеваться или противоречить (или не противоречить) другому фрагменту текста (следствию, консеквенту). Хотя эта проблема часто считается важным тестом на понимание в системах машинного обучения (ML) и была глубоко изучена для простых текстов, гораздо меньше усилий было приложено для применения таких моделей к структурированным данным, таким как веб-сайты, таблицы, базы данных и т. д. Тем не менее, распознавание семантического следования особенно актуально, когда содержимое таблицы необходимо точно суммировать и представить пользователю, и важно для таких приложений, где необходима высокая точность: в вопросно-ответных системах и виртуальных ассистентах.


В статье Understanding tables with intermediate pre-training, опубликованной в материалах EMNLP 2020, авторы представили первые методы предварительного обучения, адаптированные для анализа таблиц и позволяющие моделям учиться лучше, быстрее и на меньшем объеме данных. Авторы основываются на своей более ранней модели TAPAS, которая была расширением двунаправленной модели на основе Трансформера BERT со специальными эмбеддингами для поиска ответов в таблицах. Новые методы предварительного обучения на TAPAS позволяют получить лучшие метрики на нескольких наборах данных, включающих таблицы. В TabFact, например, сокращается разрыв между результатами выполнения этого задания моделью и человеком примерно на 50%. Авторы также систематически тестируют методы выбора подходящих входных данных для повышения эффективности, что позволяет достичь четырёхкратного увеличения скорости и снижения объема затрачиваемой памяти при сохранении 92% результатов. Все модели для разных задач и размеров выпущены в репозитории GitHub, где их можно опробовать в Colab-ноутбуке.


Семантическое следование


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


image5


Таблица вместе с несколькими предложениями из TabFact. Содержание таблицы может быть использовано для подтверждения или опровержения предложения.


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


TAPAS


Архитектура модели TAPAS использует модель BERT для кодирования предложения и плоской таблицы, считываемой строка за строкой. Для кодирования структуры таблицы используются специальные эмбеддинги. Векторный вывод первого токена используется для прогнозирования вероятности семантического следования.


Поскольку единственная информация в обучающих примерах это двоичное значение (т.е. правильно или неправильно), обучение модели для понимания того, присутствует ли в предложении семантическое следование, является нетривиальной задачей. Это в очередной раз подтверждает сложность достижения генерализации в глубоком обучении, особенно в случае достаточно скудных средств выражения необходимого значения в обучающих данных. Видя отдельные предполагаемые или опровергнутые примеры, модель может легко уловить ложные закономерности в данных. Например, в предложении Greg Norman and Billy Mayfair tie in rank (Грег Норман и Билли Мейфэр занимают равное положение) модель может выделить слово tie (галстук/связывать, разделять) вместо истинного сравнение положения Нормана и Мейфэра, необходимого для успешного применения модели за пределами исходных обучающих данных.


Методы предварительного обучения


Предварительное обучение можно использовать для прогрева моделей, предоставляя им большие объемы легко доступных неразмеченных данных. Однако предварительное обучение обычно включает в себя в основном простой текст, а не табличные данные. Фактически, TAPAS изначально был предварительно обучен с использованием простой задачи маскированного языкового моделирования, которая не предназначалась для приложений с табличными данными. Чтобы улучшить результаты модели для табличных данных, авторы представили две новые задачи бинарной классификации предварительного обучения: контрфактическую (counterfactual) и синтетическую (synthetic). Эти задачи могут применяться в качестве второго этапа предварительного обучения (часто называемого промежуточным предварительным обучением intermediate pre-training).


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


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


image1


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


Результаты


Авторы оценивают успешность контрфактического и синтетического методов предварительного обучения на наборе данных TabFact, сравнивая с базовой моделью TAPAS и двумя предыдущими моделями, которые показали хорошие результаты в области семантического следования, LogicalFactChecker (LFC) и Structure Aware Transformer (SAT). Базовая модель TAPAS демонстрирует более высокие метрики по сравнению с LFC и SAT, но предварительно обученная модель (TAPAS + CS) работает значительно лучше, достигая наивысших метрик.


Авторы также применили TAPAS + CS для вопросно-ответных задач на наборе данных SQA, для которого модель должна находить ответы из содержимого таблиц в формате диалога. Включение в задачи предварительного обучения CS повышает метрики более чем на 4 балла, демонстрируя, что этот подход также способен к генерализации за пределами семантического следования.


Accuracy


Результаты на TabFact (слева) и SQA (справа). Используя синтетические наборы данных и наборы контрфактических данных, авторы с большим отрывом достигают наивысших метрик в обеих задачах.


Данные и эффективность вычислений


Другой аспект контрфактической и синтетической задач предварительного обучения заключается в том, что, поскольку модели уже обучены для двоичной классификации, их можно применять без какой-либо тонкой настройки на TabFact. Авторы исследуют, что происходит с каждой из моделей при обучении только на поднаборе данных (или даже без них). Не увидев ни одного обучающего пример, модель TAPAS + CS способна соперничать с сильной базовой моделью Table-Bert, а при обучении всего на 10% данных, результаты становятся сопоставимы с предыдущими наивысшими метриками для этой задачи.


image7


Точность (Accuracy) на валидационном наборе данных в зависимости от объема обучающих примеров


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


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


Например, все модели, описанные выше, используют последовательности из 512 токенов, что примерно соответствует обычному порогу для модели Трансформера (хотя недавние методы повышения эффективности, такие как Reformer или Performer, доказали свою эффективность при масштабировании размера ввода). Предлагаемые в статье методы выбора столбцов позволяют ускорить обучение, сохраняя при этом высокую точность TabFact. Для 256 входных токенов получается очень небольшое снижение точности, но теперь модель можно предварительно обучить, дообучить на конкретную задачу и делать предсказания до двух раз быстрее. С 128 токенами модель по-прежнему превосходит предыдущую современную модель с еще более значительным ускорением в 4 раза быстрее по всем направлениям.


image6


Точность (Accuracy) на TabFact с использованием разной длины последовательности за счет сокращения входа с помощью предложенного метода выбора столбца.


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


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


Авторы


Подробнее..

Время, деньги и фасттекст (и причем здесь бытие)

17.02.2021 10:06:07 | Автор: admin

Пятиминутка философии на Хабре.

Все знают изречение время деньги. Но если обратиться к корпусу философских текстов, то такое сочетание для философии не самое привычное. Гораздо чаще время у философов ассоциируется, например, с бытием. Есть даже такая книга, Бытие и время. Но вдруг нас заинтересует, как же всё-таки время и деньги связаны в философии? Самое время обратиться к глубокому обучению.

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

Попробуем задать ей вопрос о сходстве денег и времени в философии. Но заодно захватим и связку бытие - время

Начнём с прояснения вопроса о взаимном сходстве двух пар понятий:

Деньги явно проигрывают бытию. Но поскольку мы задали вопрос о связи денег и времени в философии, пойдем дальше и представим бытие как негативный пример для поиска, а деньги и время как позитивный.

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

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

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru