[DS NLP] Как сделать свой BERT или расширить существующий? (Natural Language Processing)

Вопрос от коллег: Подскажите, как можно или сделать свой BERT или расширить существующий? Задача не стандартная:
1) Нужны не просто токены (слова эмбедить), а N-граммы, хотя бы до 2, т.е не просто слово "автомобиль" а еще и "легковой автомобиль".
Как я понимаю Берт содержит только по слову в классическом виде, а еще, учитывая, что он бьет BPE токенизации, так вообще плохо.
2) Нет тренировочного датасета, есть просто тексты, короткие (вроде твитера), и нужно сделать хороший эмбединг до биграмм.

Тема: Обработка естественного языка (Natural Language Processing, NLP, Data Science)

Ответы от коллег:

1) Прежде всего, рекомендую посмотреть на стоимость обучения BERT. Вот тут https://habr.com/ru/company/sberbank/blog/567776/ есть цифры.
Оригинальный BERT обучался около недели на кластере. В деньгах это много, даже с текущими зарплатами разработчиков.
Навелосипедить свою bert можно либо через библиотеку transformers (гуглить Train BERT from Scratch using Transformers in Python) или, если душевно ближе тензорфлоу, то с помощью https://github.com/CyberZHG/keras-bert. 
Вторую опцию я сам пробовал когда-то, так что это возможно, с оглядкой на п.1. Надо только снижать сложность модели, задавая облегченный конфиг, чтобы это можно было тренировать на своей GPU в обозримое время. Но даже скрученная до минимума моделька у меня тогда обучалась пару суток на 1080Ti, и это было тоскливо.
BPE там не прихоть разработчиков, а отличный компромис. Если брать даже просто слова, то нужно около 1 млн. токенов в словаре и остальное через <unk>. Для 2-грамм, да еще русских, получится около 30 млн токенов, да еще с длиннющим хвостом частот 1 в гистограмме, такое нереально обучать ни технически, ни с точки зрения качества эмбеддингов. Поэтому я бы положился на контекстность эмбеддингов в BERT, они отлично будут ловить нужные моменты с окружением слова.

2) Если хочется делать эмбеддинги n-грамм, я предлагаю такой алгоритм.
Сам давно хочу попробовать, но пока руки не доходили.
На входе: большой корпус текстов; какой-нибудь существующий BERT.
В течение 100500 шагов нужно делать следующее:
1. Выбираешь из корпуса батч текстов. Например, два текста, Мама мыла оконную раму и Столица Российской Федерации - Москва. Чем больше батч, тем лучше (дальше будет понятно, почему).
2. В каждом тексте выбираешь словосочетание, которое хочешь заэмбеддить, например, оконную раму и Российской Федерации. Выбирать их можно полностью случайно, а можно как-то ориентироваться на синтаксис, части речи и т.п.
3. В каждом тексте заменяешь выбранную n-грамму на маску, и дописываешь перед текстом id одной задачи, например, [OUT]  Мама мыла [MASK]  и [OUT] Столица [MASK] - Москва . Загоняешь эти тексты в BERT, берёшь эмбеддинги первых токенов, сохраняешь в переменную Х.
4. К каждой выбранной n-грамме дописываешь id другой задачи, прогоняешь через BERT, берёшь эмбеддинги первых токенов (это и будут эмбеддинги всей n-граммы), сохраняешь в переменную Y.
5. В чём теперь соль: мы хотим, чтобы эмбеддинг N-граммы (Y) был максимально близок (например, по косинусному расстоянию) к эмбеддингу соответствующего ему продырявленного предложения (Х), но при этом максимально далёк от эбмеддингов других продырявленных предложений. Такой вот contrastive learning. Вычислить лосс можно примерно таким кодом:

batch_size = X.shape[0]

sims = torch.matmul(torch.nn.functional.normalize(X),

                    torch.nn.functional.normalize(Y))

loss_fn = torch.nn.CrossEntropyLoss()

loss = (

    loss_fn(torch.log_softmax(sims, -1) * mult,

            torch.arange(batch_size, device=X.device))

    + loss_fn(torch.log_softmax(sims.T, -1) * mult,

            torch.arange(batch_size, device=X.device))

)


6. Делаем градиентный шаг по этому лоссу, так, чтобы обновлялся и Х, и Y.
Готовую нейросеть можно применять, создавая эмбеддинги словосочетаний так, как ты создавал Y.
Как вариант, можно обучать не одну нейросеть для X и Y, а две разные модели. Но, думаю, с одной моделью - компактнее и интереснее.

3) Эмбеддинги слов получаются из эмбеддингов бертовых wordpieces, например, макс-пулингом. никто не мешает для n-грам делать макс-пулинг по всем кусочкам. Это вряд ли может что-то улучшить, но если "такая задача", то это вполне себе решение. 
На входе вам нужно разобрать предложение на n-граммы, потом токенизировать эти n-граммы токенайзером берта, дальше соединить их в единый массив и прогнать через берт. а на выходе разобрать вектора по n-граммам и объединить.
Качество будет хорошее, модель можно будет сдать заказчику, хотя сама эта модель, на мой взгляд, не сильно полезная.

4) (автор вопроса) если так вычислительно сложно, может мне проще свой Word2Vec эмбединг сделать, всё равно предложения короткие от 2 до 5-6 слов?

5) хм. а лемматизацию вы делать собираетесь или нет? Если нет, то вектора будут плохие, т.к. многие словоформы, не говоря уж о их сочетаниях, будут встречаться 1 - 2 раза. а если будете делать лемматизацию, то чем? Если чем-то вроде pymorphy, не учитывающим контекст, то качество лемматизации будет процентов 90. Т.е.,вы сразу на входе убиваете перфоманс своей итоговой модели. а если каким-то нормальным лемматизатором, то там внутри скорее всего будет берт. и зачем вам лемматизировать бертом, чтобы сделать вектора в2в (лучше, всё-таки, фасттекст), если можно просто взять берта и сделать нормальные вектора, которые сами будут контекст учитывать?

6) (автор вопроса) я не уточнил, тексты на английском, лемматизация для такого языка не является сильно критичной. Тексты это описание должности, по простому, это вроде "Lead Python Web Developer".

Источник: ODS slack

Если Вам понравилась статья, пожалуйста, поставьте лайк, сделайте репост или оставьте комментарий. Если у Вас есть какие-либо замечания, также пишите комментарии.

Комментариев нет:

Отправить комментарий