Cross-Fold Generation или как генерировать длинные последовательности с ruGPT-3

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

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Что такое Cross-Fold Generation

RuGPT-3 - AI-модель для русского языка, которая умеет писать тексты. Она может генерировать истории, стихи и новости, которые люди не могут отличить от настоящих. Похожая модель лежит в основе Балаболы от Яндекса. В этой статье мы описываем способ генерации длинных текстов без потери смысла на примере модели ruGPT-3 Large. Мы назвали этот метод Cross-Fold Generation. С ним можно генерировать последовательности более 2000 токенов с сохранением идеи текста.

Описание работы метода и код

Сейчас при генерации выше, чем 1000 токенов, ruGPT-3 Large сильно отдаляется от первоначальной темы во время генерации, тем самым становится сложно генерировать длинные рассказы и истории.

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

  • Шаг 1: Генерируем первые N токенов

  • Шаг 2: Генерируем две последовательности с одинаковым количеством токенов, учитывая токены из шага 1

  • Шаг 3: Переносим вторую последовательность токенов в начало, а первую ставим на место второй последовательности

  • Шаг 4: Повторяем данную последовательность заданное количество раз, перенося первую последовательность вправо

Мы будем реализовывать этот метод с использованием библиотеки ​ transformers.

Установим пакет transformers версии 4.2.2(не гарантируем работоспособность на других версиях).

pip install transformers==4.2.2

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

import transformers

bos_token, eos_token = '<start>',  '<end>'
# Загрузка токенайзера
tokenizer = GPT2Tokenizer.from_pretrained("path/to/model",
                                          bos_token=bos_token,
                                          eos_token=eos_token,
                                          pad_token='<|pad|>')
# Загрузка модели
model = GPT2LMHeadModel.from_pretrained("path/to/model").cuda()
model.resize_token_embeddings(len(tokenizer))

Реализуем несколько вспомогательных функций.

Функция count_num_of_same_in_list подсчитывает количество одинаковых токенов в списке.

def count_num_of_same_in_list(arr):
    return len(arr) - len(set(arr))

Функция flatten переводит двумерный список в одномерный.

def flatten(arr):
		return [j for i in arr for j in i]

Функция generate генерирует текст с заданными параметрами top_k, top_p и другими.

def generate(input_ids, max_length=200, min_length=100):
  	# Преобразование списка токенов в их тензор
    input_ids = torch.from_numpy(np.array([input_ids])).cuda()
    # Перевод модели в режим предсказания
    model.eval()
    eos_id = tokenizer.encode(eos_token)[0]
    # Генерация последовательности токенов
    sample_outputs = model.generate(
        input_ids, 
        max_length=max_length,
        min_length=min_length, 
        do_sample=True, 
        top_k=40,
        top_p=0.92, 
        temperature=0.85,
        eos_token_id=eos_id,
        num_return_sequences=1,
        repition_penalty=3,
        num_beams=1,
        )
    return list(sample_outputs[0].cpu().numpy())

Функция generate_full_text генерирует полностью текст с выше приведённым алгоритмом.

  • prompt — строка, с которой будет продолжена генерация.

  • num_of_seq_tokens — количество последовательностей токенов. Всегда должно быть >= 3.

  • len_of_seq — количество токенов во всех последовательностях кроме первой.

  • first_toke len — количество токенов в первой последовательности.

def generate_full_text(prompt: str, num_of_seq_tokens: int = 50, len_of_seq: int = 20,
                       first_token_len: int = 20) -> str:
    # Токенезирование и преобразования в список prompt
    input_ids = list(tokenizer.encode(prompt, return_tensors='pt')[0].numpy())
    # Генерация первой последовательности токенов
    first_tokens = generate(input_ids, len(input_ids) + first_token_len, len(input_ids) + first_token_len)[
                   len(input_ids):]
    seqs_of_tokens = []
    # Генерация двух последовательностей токенов
    for i in range(2):
        # Создание последовательности токенов для генерации
        new_seq = input_ids + first_tokens + flatten(seqs_of_tokens)
        # Генерация и добавление последовательности в список
        seqs_of_tokens.append(generate(new_seq, len(new_seq) + len_of_seq, len(new_seq) + len_of_seq)[len(new_seq):])
    # Генерация оставшихся последовательностей
    for i in range(num_of_seq_tokens - 3):
        # Создание последовательности токенов для генерации
        new_seq = input_ids + flatten(
            seqs_of_tokens[:len(seqs_of_tokens) - 1][max(0, len(seqs_of_tokens) - 15):]) + first_tokens + \
                  seqs_of_tokens[len(seqs_of_tokens) - 1]
        min_same = 6483844883872
        min_list_same = []
        # Генерация и добавления последовательности в список, которое имеет минимальное совпадение
        for j in range(10):
            new_output = generate(new_seq, len(new_seq) + len_of_seq, len(new_seq) + len_of_seq)[len(new_seq):]
            count_same = count_num_of_same_in_list(new_seq + new_output)
            if count_same < min_same:
                min_same = count_same
                min_list_same = new_output
        seqs_of_tokens.append(min_list_same)
    # Соединение текста
    return tokenizer.decode(first_tokens + flatten(seqs_of_tokens))

prompt в данном случае это строка вида f'<start>{genre}<title_start>{name}<text_start>', в которой genre — это жанр текста, а name — его название.

Количество токенов высчитывается по формуле:

numoftokens = (numofseqtokens - 1)  * lenofseq + first tokenlen

Для примера мы сгенерировали два текста на 2000 токенов с одинаковыми параметрами для генерации и таким промптом '<start>комедия<title_start>Поездка<text_start>'.

Обычная генерация

Поездка на дачу. Выехали рано утром. За городом. Дорогу знали, все равно не заблудимся. Но перед поворотом на наш участок дороги резко встал трактор. Приехал откуда-то с соседней улицы и встал на обочине. В окно водитель кричит, чтобы я, не создавая помех, развернулась и уехала. Я же - в недоумении. И тут из-за трактора, высунувшись по пояс в окно, высовывается мужик и кричит: - Ты вообще кто?!! И смотрит строго. Видно, что не просто так встал. Мы с мужиком переглянулись и тут я понимаю, что он нас уже видел и знает. Сдаем назад. Я смотрю в зеркало и вижу, что он едет на нас как-то странно - чуть наклонившись на руль и опустив голову к боковым стеклам. Я уже начинаю думать, что мы тут застрянем. Смотрю в зеркало. А мужик тем временем спокойно разворачивается и едет на нас. Я поворачиваю. А мужик нас объезжает, на нас не смотрит. Я, в ужасе, в панике, стою на месте. Мужик опять же едет на нас. Но тут мужик резко останавливается, мы с ним тоже останавливаемся. Я, в ужасе, начинаю крутить рулем в разные стороны. Наконец, мужику удалось въехать в наш гараж и мы поехали дальше. Мужик едет впереди, а я сзади. По дороге смотрю - трактор все так же стоит на обочине и что-то высматривает. Мужик кричит в зеркало, но из-за пыли ничего не видно. А потом... А потом из-за дороги появляются два наших друга. Мы их увидели и, но из-за пыли ничего не увидели. И тут из окна видно. Они вылезли и начали махать руками. Один крикнул:- Что вы там встали? Давайте сворачивать. Мы сворачиваем, выезжаем. А мужики стали кричать, чтоб они ехали. А мужику видно - иди сюда! Мы им кричим:- Что ты делаешь?! А они едут! А ты?!!!. Ты где я же не видишь?! Потом мы же тебе кричу ему, чтоб разворачиваюсь, чтоб поехал в город. А ты не разворачиваюсь, поворачиваюсь и стою. Не поворачиваюсь, а он едет на нас. Но из-за пыли ничего не видно! Потом мы с криками выезжаем. Он все равно еду. Тут уж и по городу, а он на нас. Он нас едет и мы опять на нас. Я кричу его. И так до города доехали. Вот приехали. Мы его дачи. Все. Все дома. А он приехал с улицы. И с дачи. Мы сидим в машине. Я только вошли. А они заходим и по машинам. Я их выбежали. И я говорю:- А муж мне говорит:- Вы что выезжаем?- А мы же так все дома были. Мужик!!! А они все дома. Мы в гаражи. Он:- А он уехал. Мы ему кричим с мужику, что он ехал. Я так и тут. Он уже в гараже хвалились. Я с тобой. Он нам, что он так и с ними вместе ехали. Что вы приехали. И он уехал. Не надо все. Я ему кричу:- Я же сказал, что бы не ездил. А они уехали. Он нас уже по всему городу искал. Они же все это и ты видел. И по городу. Но он все равно нашел. Я ему кричу. А он еще говорит:- Нет. Мы дома. Тут мы с вами. Я выхожу, а они с ними. А они говорят:- Нет. Я тебе показал. Все. Они мне говорят:- Ты не могли бы сказать. Мы тут были! И мужику:- Ну мы все видели. Мы кричим, чтоб ты посмотрел. Он же по городу ездили. Мы ему, что мы с вами были. И не могли. Мы все кричим. Ну я его ждали. Я же видел. Я кричу. - Ты говорил, чтоб ты не приехал. Ты же их не видел. Ну я же кричу. Потом на дачи, а он уехал. И кричу:- Я уже давно говорил. Тут жена его видела. Мы с ними. Не знаю, что он там был. А они кричат:- Где вы. И я кричу. Я кричу. А они тут же в сарае сидели, а тут ты сидишь. И ты с ними был! Я еще его видел. Тут жена его увидал. А он сам поехал. Я кричу. Ну тут мы приехали и выхожу и с ними. Я думал, что он тоже, с ними. Но они уже с нами. И он мне врет. Он уже дома. А жена кричу: - Мы здесь. Я все показал. Я показываю. Жена убежала. Все. Он с ними. Я взял и кричу.- Что с тобой? Мы тоже кричу. Я с ними. И кричу. Они опять поехали. А жена мне. Что ты там был. Я кричу. Вот он и сказал. Не было. И жена уже с ними. Потом мы с ними. Но я же говорил. Я кричу. Она ушла. Жена уже. А она кричит. Я все рассказываю. Они кричат: Ну мы и там. Я кричу. И жена. Они мне показываю. Они с нами. Мы еще раз кричим. Мы не знаем, что я еще. А они в сарае сидели. Я все кричу. В городе. Тут они все. И все уехали. Все с нами. Они кричат, и мы с ними. А жена не знает. Я не спал и жена. Не помню, где. И жена тут. А мы их знаю. Он там был. Ну все же они. И я кричу. Потом он сказал: - Ты же говорил, что я буду. Я еще дома. Я кричу. Ты сам сказал. Мы с ними. Я им еще крикнул. Жена тоже с нами. Жена еще показываю. Тут он. И жена вышла. Я кричу. Они опять приехали. И мы все. И он уже приехал. Тут мы все с ними. И он уже не со мной. Я вышел. Я ему показываю. Он уже дома. Они все. А жена с ними. Я уже с ними. И потом на даче. Мы кричим, чтоб все посмотрели. Мы же сами смотрели. Он тут был. Жена тоже все показывала, а мы все. Они говорят. Мы же там были. И я все. Я им рассказал. Они говорят, что мы уже давно в сарае. И жена тут. Жена тоже. И жена кричит:- Ты это где был?! Мы с тобой вместе в сарае были. Мы с тобой и видели, мы же. И она с тобой, ты сам же мне сказал. Мы еще кричим:- В сарае. Я ее видел. Я все. Тут она же в сарае. Я ее туда и звал. Она туда и ушла. И я. Она все показывала. Я же. Она еще раз показывала. Я ей тогда тоже говорил. Она показывала. Она же и кричала. Она мне все говорила:- Там! Мы все видели. Я и показывал. Я еще говорил. Она в сарае. Я же. Но мы все видели. А ты думал, что я не знаю, как они ушли. И она же тут. Ты видел? Я тебе говорила. И жена в сарае. Я же. А ты врешь. Я тоже. Я им говорю. Мы же все тут. Я жене кричу. Они все. Я жене сказал. Я тоже. Тут жена вышла. Мы с ней вдвоем. А она кричит. Мы кричим. И она мне говорит, чтоб я показал. Я ее и показывал. А она все показывала. Я и говорю, что ее тут нет. А она все еще не знает. Жена меня спрашивает:- Ты знаешь, что такое сарай? Жена спрашивает:- Что такое сарай? - спросил я. А она говорит. - Ну сарай, это там. А мы все вместе. А потом там. Я же сам говорил. Она все это показывала. Мы все видели. И там был. Я и показывал. Тут жена пошла. Я ей кричу. Ты говорила, а я не видел. Она все не знает. Я еще раз показал. И тогда они еще с нами. Я им сказал, что знаю. Они все с нами. Потом она все говорила, чтоб я показал. Я показываю. А они говорят, что это сарай. И я ее показывал. Вот она. Она сама говорила. Тут жена пришла. Мы уже с ними. Жена говорит, чтоб я спал. Мы еще все вместе. Они все в сарае. Тут жена говорит, что мы все вместе. А сараем они называют сарай, где много дверей. Там они все. И туда кладут. Они из палочек. В них палочки. Это они для того, чтоб двери открывать. И они для того, чтоб туда входить. И мы в дверь входим. И там дверь. Я ее открыл. И мы все видели. Потом я туда вошел. Мы все смотрели. Все смотрели. А потом там был диван. И потом стол. На столе тоже палочки. И палочки. Они все для того. Чтоб дверь открывать. Потом в дверь вошла эта женщина. Она вся такая. Только я не видел лица. Она в белом платье и белом фартуке. Она меня спрашивает:- Ты мальчик?- Я мальчик. А ты? Тут я вспомнил, что я же с ними ехал. И опять я им сказал:- Ты мальчик?- Я мальчик.- Ты тоже мальчик?

Cross-Fold Generation

Поездка на дачу. Выехали рано утром. За городом. Дорогу знали, все равно не заблудимся. Едем. Я на своем мотоцикле, Саня на своем скутере. Приехал на место где-то полдесятого. Саша решил покататься вокруг. Вокруг дачи много насажено разных цветов. Все эти цветы на даче сажали лет пять назад. Все высохли, кроме одной розы. Она была как новенькая, только что из клумбы. На даче ее оставили для красоты. Цветок не простой. У него на верхушке есть желтый цветок. И это не астры. У нее очень длинные зеленые лепестки и они все в крапинках. А еще она вся в бусах. Саше нравится, когда она цветет. Он ей тоже подарил букет цветов. Мы поехали по проселочной дороге. Не очень хорошей дороге. Но ехать надо. С нами была моя двоюродная сестра Маша. Ей надо было ехать в другой город. Машка была любительницей скорости. Она не боялась наматывать на мотоцикл лишние километры. По этой причине она взяла с собой только телефон и навигатор. Мне же в дорогу дали с собой книжку и плеер. Очень удобно, особенно в книжке. Я люблю слушать музыку, и без музыки очень хорошо усну. Маша была за рулем. А у меня в плеере был встроен маленький радиоприемник, чтоб слушать музыку. Сначала я ехал тихо, потом песня начала надоедать. Я начал орать на всю дорогу, чтоб ехали помедленней. Потом на дороге начался страшный шум. И тут мы въехали в туман. Все стало очень темно, хоть глаз выколи. Пришлось остановиться. Я вышел из машины. Посмотрел на небо. На небе высыпали звезды. Они все разные. Некоторые очень яркие. Другие, как будто их кто-то рисовал. Маша включила дальний свет. В темноте было уже хотела вырулить, как вдруг в машине включился свет. Оказалось, что это сигнал светофор. Светофор всегда горел зеленым. Мы стали мигать красным и ехать через двойную сплошную. Туман рассеялся. И стало не так видно. Тут мы увидели впереди нас старый покосившийся знак. На нем было написано "ДАЧА". Мы помчались туда. Я увидел красивый деревянный домик. Я позвонил. Дверь открыл мне мальчик. Я спросил, чей это дом. Он ответил, что это дача его мамы. Она уехала, но сейчас вернется. Я пошел на кухню. Там было все так красиво. На столе стояли цветы. Посуда блестела, скатерть была в красивой серебряной посуде. Но больше всего понравилась серебряная ложка. Я взял ее и понес в комнату. Только там ее. На ней был какой-то зверек с золотыми глазами. Он прыгнул на стол. Налетел ветерок, он упал на пол и стал на ложку. Поднял голову. Потом он голову и говорит:- В чем дело?- Это вилка, - говорит. Как я ее поднял? Она сама подняла! Вынул вилку из розетки. Я ее положил в карман. Он говорит:- А машина?- Да, машина не заводится. Где ты видел, где стоит генератор?- Здесь.- Ну ты встань на дорогу. Он тебя заведется. Я встал, а у обочины. И поехал. Но тут увидел перед нами стоял мужик. Я встал как вкопанный. Мужик говорит:- Ты зачем встал?- Дай денег. Я дал. Он уехал. Я вышел из машины, сел за руль, завел. Выжал сцепление. Посмотрел на дорогу. Ничего. Едем мы медленно едем. Ничего. Туман. Ничего не видно. Наконец, на первом километре въехали в поле. Тут уже был свет. Вышел мужчина. У него в руках ведро. Он его забрал. Я спросил у него деньги. Он мне дал мешок. Он сказал: - Спасибо. Я побежал к дому. Я взял мешок. Пришел домой, залез в мешок. Включил свет, начал его распаковывать. Вдруг из мешка стали вылезать черви. Их стало много. Я выкинул их из машины. Встал и выбросил из машины. Они стали опять появляться. Приехали в лес. Вышел на поляну. Там был ручей. А у ручья сидел мужчина с удочки, и рыбачил. Я его спрашиваю:- Мужчина, как пройти к реке?- Не знаю. Я сам в город еду. Меня жена там ждет.- Ну, и я одна не поеду. Я вам свой телефон дам. А вдруг что-нибудь случится. - А может не надо? - Надо.- Ну ладно, возьму.- Ну вот и телефон. На даче мы и решили ехать на машине. Доехали до города. Забрали бабушку. Она нам дала телефон. Мы с ним пошли. Мы уже с ним гуляли. Потом обратно. По дороге зашли в магазин. Там все взяли пива. Попили. Пошли по дороге. Начали разговаривать о жизни. Вдруг видим - стоит девушка. Очень красивая. Мы с ней рядом. Подходим к ней, и спрашиваем:- Девушка, вы не знаете, где тут до реки можно доехать?- Да, не знаю. Только далеко.- Ну, но зато, отдохнете. Я ей: - А можно я у вас на машине покатаюсь? Она сказала: - Да, пожалуйста. Мы сели на заднее сидение, и поехали. Она стала ей рассказывать про дорогу. Мы с ней разговаривали. Вдруг, а она молчала. Смотрела на дорогу, как будто что-то высматривала. Потом сказала: "Прямо". Я остановился. Она пошла в сторону. Я встал, и пошел за ней. Смотрю, а она мне навстречу мужик. Огромный такой здоровенный, как шкаф. У него глаза большие руки, и нос крючком. Он нас сразу же затолкал. Он стоял и смотрел на нас. Мне говорит: - Не бойся. Тебя не тронут. Но и твоих друзей тоже. Не знаю. Мы просто их ждали. Они не успели. Там река очень бурная. Я им сказал:- А почему они такие большие? Они говорят: - Так у них всегда так. Это они, когда домой возвращаются. Я им надо сначала в квартире убраться. А потом выезжать. Мы с бабушкой пошли к реке. Но там они нас не пускали. Там уже было полно машин. И мы стали искать тех, кого можно было пустить. Я нашел. Бабушка говорит:- Гляди, видишь, в конце дороги, дом. Это и есть наша дача. Я только в окно выгляну. Надо бабушку попросить, чтобы нас не трогали. Я буду кричать. Но они стали кричать все громче и громче. И тут мы остановились. Прямо перед нашим домом стояла машина. А около воды. И в канаву переехала. И остановилась. И из нее дядя высунулась голова и руками замахала. Бабушка кричит:- Дядя, дядя, вы выходите! Мы сейчас вас заберем. А дядя, дядя, не надо выходит. Бабушка говорит, сейчас сама выведет. И все же выскочил .Бабушка его на него не по-другому ругалась. А в поездках по-настоящему, когда мы его увидели, он мне стало даже страшно. Он стал как-то огромный. И совсем игрушечный. А бабушка его все равно стали звать:- Ну, Садись, Садись, Сашенька, поехали. Сели. Он сам. А он сказал:- А где же пирожки? Они в термосе остались. Я вам их в вагончике оставил. Я сказал:- Вот смешно! А термос в поезде оставили. А потом обратно не могли бы? Бабушка говорит:- А ты на крышу лезь. Там холодно. Тут уже солнышко стало и дождик накрапывать .И мы. А по полю мы ехали, а в поле травам по полям. И прямо по самой середине колею. Это мы на станцию приехали. Только очень скоро ехали. Я спросил:- А почему станция такая большая? А бабушка сказала:- Потому что там станция. Мы по главной, а станция вон впереди. А впереди еще другая. А станция, а кругом дачи. И трава, как коврики. И дома все белые. И солнце. И заборы. И ворота. И дядя-водитель говорит:- Ну вот, сейчас чай пить будем. А я бабушке позвоню .Бабушка сказала:- Я только молочка возьму. И дала и в дорогу пирожков купила. И баранок. Мы вышли в поле. А кругом дачные домики.

Заключение

Как можно заметить вариант с Cross-Fold Generation допустил меньше повторов и больше имеет связи с началом текста. У ruGPT-3 есть и другие проблемы, но в этой статье мы решили одну из них.

Авторы

@Арсений Шахматов | @Степан Шабалин | @Максим Герасимов

Источник: https://habr.com/ru/post/572940/


Интересные статьи

Интересные статьи

Начну с того, что я совсем недавно начала погружаться в IT в целом и Ruby в частности, и это задание мне выдали в качестве тестового для получения места на стажировке. З...
У некоторых бизнес-тренеров в области е-коммерса и консультантов по увеличению интернет-продаж на многие вопросы часто можно слышать универсальную отмазку — «надо тестировать» или другую (чтобы не...
Много всякого сыпется в мой ящик, в том числе и от Битрикса (справедливости ради стоит отметить, что я когда-то регистрировался на их сайте). Но вот мне надоели эти письма и я решил отписатьс...
Периодически мне в разных вариантах задают вопрос, который «в среднем» звучит так: «что лучше: заказать интернет-магазин на бесплатной CMS или купить готовое решение на 1С-Битрикс и сделать магазин на...
1С Битрикс: Управление сайтом (БУС) - CMS №1 в России по версии портала “Рейтинг Рунета” за 2018 год. На рынке c 2003 года. За это время БУС не стоял на месте, обрастал новой функциональностью...