Создание историй Рика и Морти с помощью GPT2 с использованием Transformers и Streamlit

В этом посте будет показано, как настроить предварительно обученную модель GPT2 на стенограммах Рика и Морти с помощью библиотеки Transformers Hugging Face, создать демонстрационное приложение и развернуть его с помощью Streamlit обмена.

Вступление

С быстрым прогрессом в машинном обучении (ML) и обработке естественного языка (NLP) новые алгоритмы могут генерировать тексты, которые кажутся все более и более рукотворными. Один из таких алгоритмов, GPT2¹, использовался во многих приложениях с открытым исходным кодом². GPT2 был обучен на WebText, который содержит 45 миллионов исходящих ссылок с Reddit (то есть веб-сайтов, на которые ссылаются комментарии). В десятку самых популярных исходящих доменов³ входят Google, Archive, Blogspot, Github, NYTimes. , WordPress, Washington Post, Wikia, BBC и The Guardian. Предварительно обученную модель GPT2 можно настроить на определенных наборах данных, например, чтобы «усвоить» стиль набора данных или научиться классифицировать документы. Это осуществляется с помощью Transfer Learning, который можно определить как «средство извлечения знаний из исходной настройки и применения их к другой целевой настройке» ⁴. Подробное описание GPT2 и его архитектуры см. В исходной статье⁵, в блоге OpenAI⁶ или в иллюстрированном руководстве Джея Аламмара⁷.

Набор данных

Набор данных, используемый для точной настройки GPT2, состоит из первых трех сезонов Расшифровки стенограмм Рика и Морти. Я отфильтровал все диалоги, которые не были созданы Риком, Морти, Саммером, Бет или Джерри. Данные были загружены и сохранены в необработанном текстовом формате. Каждая строка представляет говорящего и его высказывание или описание действия / сцены. Набор данных был разделен на обучающие и тестовые данные, которые содержат 6905 и 1454 строк соответственно. Необработанные файлы можно найти здесь. Обучающие данные используются для точной настройки модели, а тестовые данные используются для оценки.

Обучение модели

Библиотека Transformers Hugging Face предоставляет простой скрипт для тонкой настройки пользовательской модели GPT2. Вы можете настроить свою собственную модель с помощью этого блокнота Google Colab. После завершения обучения вашей модели убедитесь, что вы загрузили папку вывода обученной модели, содержащую все соответствующие файлы модели (это необходимо для загрузки модели позже). Вы можете загрузить свою собственную модель на Hugging Face's Model Hub⁸, чтобы сделать ее общедоступной. Модель получает оценку сложности около 17 при оценке на тестовых данных.

Сборка приложения

Для начала давайте создадим новую папку проекта с именем Story_Generator и виртуальную среду для Python 3.7:

mkdir Story_Generator
cd Story_Generator
python3.7 -m venv venv
source venv/bin/activate

Далее мы хотим установить все зависимости для проекта:

pip install streamlit-nightly==0.69.3.dev20201025
pip install torch==1.6.0+cpu torchvision==0.7.0+cpu -f https://download.pytorch.org/whl/torch_stable.html
pip install git+git://github.com/huggingface/transformers@59b5953d89544a66d73

Все наше приложение будет жить внутри app.py. Давайте создадим его и импортируем наши недавно установленные зависимости:

import urllib
import streamlit as st
import torch
from transformers import pipeline

Прежде чем мы выполним какую-либо обработку, мы хотим, чтобы наша модель загрузилась. Используя декоратор @st.cache, мы можем выполнить функцию load_model() один раз и сохранить результат в локальном кеше. Это повысит производительность нашего приложения. Затем мы можем использовать функцию pipeline(), чтобы просто загрузить модель для генерации текста (замените путь модели на вашу пользовательскую модель или используйте мою предварительно обученную модель из Model Hub):

@st.cache(allow_output_mutation=True, suppress_st_warning=True)
def load_model():
    return pipeline("text-generation", model="e-tony/gpt2-rnm")
model = load_model()

Мы можем использовать функцию text_area() Streamlit, чтобы создать простое текстовое поле. Мы можем дополнительно указать высоту и максимально допустимые символы (поскольку для создания большого текста требуется больше времени):

textbox = st.text_area('Start your story:', '', height=200, max_chars=1000)

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

streamlit run app.py

Затем мы можем добавить виджет ползунка, чтобы пользователь мог определить, сколько символов должна генерировать модель:

slider = st.slider('Max story length (in characters)', 50, 200)

Теперь мы готовы сгенерировать текст! Давайте создадим кнопку, которая выполняет генерацию текста:

button = st.button('Generate')

Мы хотим, чтобы наше приложение прослушивало действие «нажатие кнопки». Это можно сделать с помощью простого условного оператора. Затем мы можем сгенерировать текст и вывести его на экран:

if button:
    output_text = model(textbox, max_length=slider)[0]['generated_text']
 
    for i, line in enumerate(output_text.split("\n")):
        if ":" in line:
            speaker, speech = line.split(':')
            st.markdown(f'__{speaker}__: {speech}')
        else:
            st.markdown(line)

Давайте введем подсказку в текстовое поле и создадим историю:

Rick: Come on, flip the pickle, Morty. You're not gonna regret it. The payoff is huge.

Выход:

Rick: Come on, flip the pickle, Morty. You're not gonna regret it. The payoff is huge. You don't have to be bad, Morty.
(Rick breaks up)
[Trans. Ext. Mortys home]

Большой! Модель выводит новый текст и выглядит неплохо. Мы можем улучшить качество вывода, настроив параметры для метода декодирования. См. Сообщение Hugging Face о декодировании⁹ для подробного обзора различных методов. Давайте заменим нашу model() функцию и применим еще несколько аргументов:

output_text = model(textbox, do_sample=True, max_length=slider, top_k=50, top_p=0.95, num_returned_sequences=1)[0]['generated_text']

Короче говоря, do_sample случайным образом выбирает следующее слово, top_k фильтрует наиболее вероятные k следующих слов, top_p позволяет динамически увеличивать и уменьшать количество возможных следующих слов, а num_returned_sequences выводит несколько независимых выборок (в нашем случае всего 1) для дальнейшей фильтрации или оценки. Вы можете поиграть со значениями, чтобы получить разные типы выходных данных.

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

Выход:

Rick: Come on, flip the pickle, Morty. You're not gonna regret it. The payoff is huge.
Morty: Ew, no, Rick! Where are you?
Rick: Morty, just do it! [laughing] Just flip the pickle!
Morty: I'm a Morty, okay?
Rick: Come on, Morty. Don't be ashamed to be a Morty. Just flip the pickle.

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

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

def load_bad_words() -> list:
    res_list = []
file = urllib.request.urlopen("https://raw.githubusercontent.com/RobertJGabriel/Google-profanity-words/master/list.txt")
    for line in file:
        dline = line.decode("utf-8")
        res_list.append(dline.split("\n")[0])
    
    return res_list
BAD_WORDS = load_bad_words()
    
def filter_bad_words(text):
    explicit = False
    
    res_text = text.lower()
    for word in BAD_WORDS:
        if word in res_text:
            res_text = res_text.replace(word, word[0]+"*"*len(word[1:]))
            explicit = True
if not explicit:
        return text
output_text = ""
    for oword,rword in zip(text.split(" "), res_text.split(" ")):
        if oword.lower() == rword:
            output_text += oword+" "
        else:
            output_text += rword+" "
return output_text
output_text = filter_bad_words(model(textbox, do_sample=True, max_length=slider, top_k=50, top_p=0.95, num_returned_sequences=1)[0]['generated_text'])

Наш последний app.py файл теперь выглядит так:

import urllib
import streamlit as st
import torch
from transformers import pipeline
def load_bad_words() -> list:
    res_list = []
file = urllib.request.urlopen("https://raw.githubusercontent.com/RobertJGabriel/Google-profanity-words/master/list.txt")
    for line in file:
        dline = line.decode("utf-8")
        res_list.append(dline.split("\n")[0])
    
    return res_list
BAD_WORDS = load_bad_words()
    
@st.cache(allow_output_mutation=True, suppress_st_warning=True)
def load_model():
    return pipeline("text-generation", model="e-tony/gpt2-rnm")
def filter_bad_words(text):
    explicit = False
    
    res_text = text.lower()
    for word in BAD_WORDS:
        if word in res_text:
            res_text = res_text.replace(word, word[0]+"*"*len(word[1:]))
            explicit = True
if not explicit:
        return text
output_text = ""
    for oword,rword in zip(text.split(" "), res_text.split(" ")):
        if oword.lower() == rword:
            output_text += oword+" "
        else:
            output_text += rword+" "
return output_text
model = load_model()
textbox = st.text_area('Start your story:', '', height=200, max_chars=1000)
slider = slider = st.slider('Max text length (in characters)', 50, 1000)
button = st.button('Generate')
if button:
    output_text = filter_bad_words(model(textbox, do_sample=True, max_length=slider, top_k=50, top_p=0.95, num_returned_sequences=1)[0]['generated_text'])
 
    for i, line in enumerate(output_text.split("\n")):
        if ":" in line:
            speaker, speech = line.split(':')
            st.markdown(f'__{speaker}__: {speech}')
        else:
            st.markdown(line)

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

Теперь он готов к работе!

Развертывание приложения

Приложение можно развернуть с помощью Streamlit Sharing¹⁰. Вам просто нужно иметь общедоступный репозиторий Github с requirements.txt и app.py файлами в вашем репозитории. Ваш requirements.txt файл должен выглядеть примерно так:

-f https://download.pytorch.org/whl/torch_stable.html
streamlit-nightly==0.69.3.dev20201025
torch==1.6.0+cpu
torchvision==0.7.0+cpu
transformers @ git+git://github.com/huggingface/transformers@59b5953d89544a66d73

На веб-сайте Streamlit Sharing вы можете просто связать свой репозиторий, и ваша модель скоро будет доступна!

Этические соображения

Приложение, представленное в этом посте, предназначено только для развлекательных целей! Следует внимательно рассмотреть возможность применения модели GPT2 в других сценариях. Хотя некоторые домены были удалены из исходных обучающих данных, модель GPT2 была предварительно обучена на практически неотфильтрованном контенте из Интернета, который содержит предвзятые и разборчивые выражения. Образцовая карта OpenAI указывает на следующие соображения:

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

- Помощь при написании: помощь по грамматике, автозаполнение (для обычной прозы или кода)

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

- Развлечения: создание игр, чат-ботов и забавные поколения.

Варианты использования вне рамок:

Поскольку крупномасштабные языковые модели, такие как GPT-2, не различают факты от вымысла, мы не поддерживаем варианты использования, требующие, чтобы созданный текст был правдой. Кроме того, языковые модели, такие как GPT-2, отражают предубеждения, присущие системам, на которых они были обучены, поэтому мы не рекомендуем их развертывать в системах, которые взаимодействуют с людьми, если разработчики сначала не проведут исследование предубеждений, относящихся к предполагаемому использованию. -кейс. Мы не обнаружили статистически значимых различий в тестах на гендерные, расовые и религиозные предубеждения между 774M и 1.5B, что означает, что ко всем версиям GPT-2 следует подходить с одинаковой осторожностью в случаях использования, которые чувствительны к предубеждениям, связанным с человеческими атрибутами.

Пример ниже показывает, как модель может иметь предвзятые прогнозы (другой пример можно найти здесь):

>>> from transformers import pipeline, set_seed
>>> generator = pipeline('text-generation', model='gpt2')
>>> set_seed(42)
>>> generator("The man worked as a", max_length=10, num_return_sequences=5)
[{'generated_text': 'The man worked as a waiter at a Japanese restaurant'},
 {'generated_text': 'The man worked as a bouncer and a boun'},
 {'generated_text': 'The man worked as a lawyer at the local firm'},
 {'generated_text': 'The man worked as a waiter in a cafe near'},
 {'generated_text': 'The man worked as a chef in a strip mall'}]
>>> set_seed(42)
>>> generator("The woman worked as a", max_length=10, num_return_sequences=5)
[{'generated_text': 'The woman worked as a waitress at a Japanese restaurant'},
 {'generated_text': 'The woman worked as a waitress at a local restaurant'},
 {'generated_text': 'The woman worked as a waitress at the local supermarket'},
 {'generated_text': 'The woman worked as a nurse in a health center'},
 {'generated_text': 'The woman worked as a maid in Daphne'}]

Я призываю читателя внимательно рассмотреть применение и использование таких моделей в реальных сценариях. Существует множество ресурсов (например, EML¹¹, AINow¹²) для изучения этического машинного обучения.

Заключение

Поздравляю! Ваше приложение запущено!

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

Вы можете протестировать демо или просмотреть проект на Github.

использованная литература

[1]: GPT2

[2]: 30 лучших проектов с открытым исходным кодом Gpt2

[3]: Состояние трансфертного обучения в НЛП

[4]: 1000 самых популярных доменов, присутствующих в WebText

[5]: А. Рэдфорд, Джеффри Ву, Р. Чайлд, Дэвид Луан, Дарио Амодеи и Илья Суцкевер, 2019. Языковые модели - это многозадачные ученики без учителя.

[6]: Лучшие языковые модели и их последствия

[7]: Иллюстрированный GPT-2 (Визуализация языковых моделей-трансформеров)

[8]: Обмен и загрузка модели

[9]: Как сгенерировать текст: использование различных методов декодирования для генерации языка с помощью Transformers

[10]: Развернуть приложение

[11]: Институт этического искусственного интеллекта и машинного обучения

[12]: AI Now Institute