This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.
This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.
" - [nlp for you](https://lena-voita.github.io/nlp_course/language_modeling.html)\n",
"\n",
" - [YSDA Natural Language Processing course](https://github.com/yandexdataschool/nlp_course/tree/2023/week03_lm)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "H2QPNriQIvTw"
},
"source": [
"# Лабораторная работа №4. Использование нейронных сетей для генерации текста\n",
"В ходе работы мы будем обучать нейронные сети генерировать тексты, похожие на стихи поэтов.\n",
"\n",
"## Цель работы\n",
"Получить практические навыки решения задачи генерации текста.\n",
"\n",
"## Указания\n",
"1. Для работы рекомендуется использовать Google Colab и среду с GPU для ускорения расчетов. Для установки среды, использующей GPU в Google Colab нужно выбрать пункт меню \"Среда выполнения\" -> \"Сменить среду выполнения\" -> выбрать аппаратный ускоритель \"GPU\".\n",
"\n",
"2. Выполнять работу следует последовательно запуская ячейки, анализируя код и приведенные комментарии и разъяснения.\n",
"\n",
"3. В ходе работы будут встречаться вопросы, на которые нужно ответить, создав после него новую ячейку. Вопросы отмечены заголовками 3-го уровня.\n",
"Для ответа досточно 1-2 предложений. Но будьте готовы более подробно его пояснить при устной беседе.\n",
"\n",
"4. Обращайте внимание на комментарии `<your choice here>` - здесь вам нужно будет вставить значения параметров либо исходя из анализа кода\\выборки (где указано), либо попробовать разные варианты. Парамеры, приведенные тут по умолчанию, не обязательно правильные.\n",
"\n",
"## Варианты заданий\n",
"\n",
"Четные номера по журналу - Пушкин, нечетные - Маяковский.\n",
"При использовании Google Colab следует выбрать среду выполнения с аппаратным ускорителем GPU, что существенно ускорит расчеты. Для установки среды, использующей GPU в Google Colab нужно выбрать пункт меню \"Среда выполнения\" -> \"Сменить среду выполнения\" -> выбрать аппаратный ускоритель \"GPU\". При этом, следующая ячейка, проверяющая доступность CUDA (платформы, использующей графические ускорители), должна возвращать `True`"
"Где можно будет использовать знание о параметрах распределения длин в выборке?"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "zfkSHd-8FlE1"
},
"source": [
"Выберите длину для генерации"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"id": "y-Q7-EPuFZO5"
},
"outputs": [],
"source": [
"# Устанавливаем, сколько символов будет генерировать модель (максимальная длина генерируемого текста)\n",
"MAXLEN = 512 #<your choice here>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "TSfv4n08Fpiv"
},
"source": [
"# Преобразование данных"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "vtelHtjyFq1N"
},
"source": [
"Создадим функцию для преобразования текста в вектора одинакового размера для подачи в нейросеть. В этой функции добавляется токен EOS - конец последовательности. Если текст короче заданной длины, то добавляется специальный токен PAD. Если текст больше заданной длины, то он обрезается."
"Пояснить, почему получен такой размер `emb_out`?"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "nkS5ZQ8fF4XP"
},
"source": [
"# Создадим ячейку GRU"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ZBfkiOYmM9yr"
},
"source": [
"### Вопрос 5\n",
"Обратиться к документации к [nn.GRU](https://pytorch.org/docs/stable/generated/torch.nn.GRU.html) и ответить на вопрос, за что отчечают параметры `input_size`, `hidden_size`, `num_layers`.\n",
"\n",
"В следующей ячейке задать значения для этих параметров"
"Каждый раз, когда вы вызываете модель (forward), вы передаете некоторый текст и внутреннее состояние. Модель возвращает прогноз для следующего символа и его нового состояния.\n",
" p_next = F.softmax(logp_next / temperature, dim=-1).data.numpy()[0] # нормируем выходы модели на температуру и применяем софтмакс\n",
"\n",
" if strategy == \"greedy\": next_ix = p_next.argmax() #берем токен с максимальной вероятностью\n",
" elif strategy == \"sample\": next_ix = np.random.choice(len(id2char), p=p_next[0]) #получаем следующий токен сэмплированием с вероятностями\n",
" else: raise ValueError('Хулиган, не делай так! Выбери \"greedy\" или \"sample\"')\n",
"\n",
" if id2char[next_ix] == \"EOS\": break # если получили токен EOS, то прекращаем генерацию\n",
" else:\n",
" next_ix = torch.tensor([[next_ix]], dtype=torch.int64) # создаем тензор следующий буквы\n",
" x_sequence = torch.cat([x_sequence, next_ix], dim=1) # добавляем предсказанный токен в конец последовательности\n",
"\n",
" return ''.join([id2char[ix] for ix in x_sequence.data.numpy()[0]]) # возвращаем декодированную строку"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "IJohkcbJGG5O"
},
"source": [
"Попробуем что-нибудь сгенерировать:"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "b2hqsv33P2gW"
},
"source": [
"### Вопрос 7\n",
"Выполните следующую ячейку несколько раз с одной и той же SEED_PHRASE, запомните выводы модели и объясните результат - чем отличается стратегия greedy от sample?"
"# Функция потерь - negative log likelihood loss (NLL Loss) - используеся для задачи многоклассовой классификации в ситуации, когда имеются логарифмы вероятностей каждого класса\n",
"# NLL Loss эквивалентен CrossEntropyLoss, но применяется к логарифмам вероятностей - у нас на выходе линейного слоя как раз исопользуется функция softmax\n",
"\n",
"summary(model)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "2nwqarvRGPY0"
},
"source": [
"Необученная модель не может делать адекватные предсказания. Ее перплексия («коэффициент неопределённости») приблизительно равна размеру словаря. Это говорит о полной неопределенности модели при генерации текста."
"['Буря мглою небо кроет,\\nВихри снежные крутя:\\nТо, как зверь, она завоет,\\nТо заплачет, как дитя,\\nТо по кровле обветшалой\\nВдруг соломой зашумит,\\nТо, как путник запоздалый,\\nК нам в окошко застучит.\\n\\nНаша ветхая лачужка\\nИ печальна, и темна.\\nЧто же ты, моя старушка,\\nПриумолкла у окна?\\nИли бури завываньем\\nТы, мой друг, утомлена,\\nИли дремлешь под жужжаньем\\nСвоего веретена?\\n\\nВыпьем, добрая подружка\\nБедной юности моей,\\nВыпьем с горя; где же кружка?\\nСердцу будет веселей.\\nСпой мне песню, как синица\\nТихо за морем жила;\\nСпой мне песню, как девица\\nЗа водой поутру шла.\\n\\nБуря мглою небо кроет,\\nВихри снежные крутя;\\nТо, как зверь, она завоет.\\nТо заплачет, как дитя.\\nВыпьем, добрая подружка\\nБедной юности моей,\\nВыпьем с горя; где же кружка?\\nСердцу будет веселей.\\n\\n']"
"example = torch.tensor(to_matrix(train[:1], char2id, max_len=MAXLEN)) # Возьмем первый документ и попробуем сгененировать этот же текст начиная с первой буквы\n",
"device = 'cpu' # Обучение обычно проводится на GPU, но чтобы не тратить его ресурсы, работу по генерации текста уже обученной моделью стоит перенести обратно на CPU\n",
"model.to(device)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "0PWo3arcGWvv"
},
"source": [
"Погенерируем текст. Сначала зададим стартовую фразу.\n",
"\n",
"Выполнив следующие ячейки по несколько раз, убедитесь, что правильно ответили на вопрос 7 \"Чем отличается стратегия greedy от sample?\""
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {
"id": "dtUW5hVBGX9p"
},
"outputs": [],
"source": [
"#Если вы не согласны, вы можете поменять стартовую фразу, но что думает об этом высказывание машина?\n",
"SEED_PHRASE = 'Теория автоматического управления - лучший предмет,'"
"print(generated_text) #Так работает предобученный трансформер"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "By_ukdhOp_F8"
},
"source": [
"Давайте дообучим трансформер на нашем датасете - мы хотим генерировать стихи"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"id": "YLIYVZ1AGxzV"
},
"outputs": [],
"source": [
"train_path = \"train_dataset.txt\"\n",
"\n",
"with open(\"poems.txt\", encoding=\"utf-8\") as file:\n",
" data = file.read().split(\"</s>\\n\\n\")\n",
"\n",
"with open(train_path, mode=\"w\", encoding=\"utf-8\") as f:\n",
" f.write(\"\".join(data))"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"id": "ulVcdoROGyy2"
},
"outputs": [],
"source": [
"train_dataset = TextDataset(tokenizer=tokenizer, file_path=train_path, block_size=128) # Создание датасета\n",
"data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False) # Создание даталодера (нарезает текст на оптимальные по длине куски)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"id": "nTkPLolOGzqH"
},
"outputs": [],
"source": [
"\n",
"training_args = TrainingArguments(\n",
" output_dir=\"./finetuned\",\n",
" overwrite_output_dir=True,\n",
" num_train_epochs=15,\n",
" per_device_train_batch_size=32,\n",
" per_device_eval_batch_size=32,\n",
" warmup_steps=10, # рекомендованные значения (warmup_steps нужен для \"разогрева\" сети, c его помощью learning rate постепенно увеличивается до заданного значения)\n",
"#(обычно мы хотим положить батч по-больше, чтобы сеть побыстрей сошлась, но мы ограничены памятью gpu, gradient_accumulation_steps накапливает (суммирует или усредняет) градиенты за прогон на 16 батчах )\n",
"Какое значение перплексии получилось у трансформера?\n",
"\n",
"Какое значение перплексии получалось у рекуррентной сети?\n",
"\n",
"Почему у рекуррентной сети значение было существенно ниже, но качество текстов хуже? Почему нельзя сравнивать значения для рекуррентной сети и трансформера?\n"
"Проверьте работу ячейки выше для разных стартовых фраз и разных параметров `temperature`, `max_length`, `do_sample` и объясните, за что отвечает каждый из параметров. Подберите (субъективно) лучшие"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "URWWyeN_G92m"
},
"source": [
"# Beam Search\n",
"До этого мы использовали обычный \"жадный\" поиск\n",
"Для использования Beam search передадим в функцию генерации параметр `num_beams` который характеризует количество рассматриваемых \"альтернативных\" путей"
]
},
{
"cell_type": "code",
"execution_count": 105,
"metadata": {
"id": "f5qj2KVoG56d"
},
"outputs": [],
"source": [
"SEED_PHRASE = 'Теория автоматического управления - лучший предмет,'\n",