411 lines
28 KiB
Plaintext
411 lines
28 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Генеративные сети\n",
|
||
"\n",
|
||
"Рекуррентные нейронные сети (RNN) и их варианты с управляемыми ячейками, такие как ячейки долгой краткосрочной памяти (LSTM) и управляемые рекуррентные блоки (GRU), предоставляют механизм для языкового моделирования, то есть они могут изучать порядок слов и предсказывать следующее слово в последовательности. Это позволяет использовать RNN для **генеративных задач**, таких как обычная генерация текста, машинный перевод и даже создание подписей к изображениям.\n",
|
||
"\n",
|
||
"В архитектуре RNN, которую мы обсуждали в предыдущем разделе, каждая единица RNN выдавала следующее скрытое состояние в качестве результата. Однако мы также можем добавить еще один выход к каждой рекуррентной единице, что позволит нам выдавать **последовательность** (равную по длине исходной последовательности). Более того, мы можем использовать RNN-единицы, которые не принимают входные данные на каждом шаге, а просто используют некоторый начальный вектор состояния и затем генерируют последовательность выходных данных.\n",
|
||
"\n",
|
||
"В этом блокноте мы сосредоточимся на простых генеративных моделях, которые помогают нам генерировать текст. Для упрощения давайте построим **символьную сеть**, которая генерирует текст по буквам. Во время обучения нам нужно взять некоторый текстовый корпус и разбить его на последовательности букв.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Loading dataset...\n",
|
||
"Building vocab...\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"import torch\n",
|
||
"import torchtext\n",
|
||
"import numpy as np\n",
|
||
"from torchnlp import *\n",
|
||
"train_dataset,test_dataset,classes,vocab = load_dataset()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Создание словаря символов\n",
|
||
"\n",
|
||
"Для построения генеративной сети на уровне символов необходимо разбить текст на отдельные символы, а не на слова. Это можно сделать, определив другой токенизатор:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Vocabulary size = 82\n",
|
||
"Encoding of 'a' is 1\n",
|
||
"Character with code 13 is c\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"def char_tokenizer(words):\n",
|
||
" return list(words) #[word for word in words]\n",
|
||
"\n",
|
||
"counter = collections.Counter()\n",
|
||
"for (label, line) in train_dataset:\n",
|
||
" counter.update(char_tokenizer(line))\n",
|
||
"vocab = torchtext.vocab.vocab(counter)\n",
|
||
"\n",
|
||
"vocab_size = len(vocab)\n",
|
||
"print(f\"Vocabulary size = {vocab_size}\")\n",
|
||
"print(f\"Encoding of 'a' is {vocab.get_stoi()['a']}\")\n",
|
||
"print(f\"Character with code 13 is {vocab.get_itos()[13]}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Давайте посмотрим пример того, как мы можем закодировать текст из нашего набора данных:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"tensor([ 0, 1, 2, 2, 3, 4, 5, 6, 3, 7, 8, 1, 9, 10, 3, 11, 2, 1,\n",
|
||
" 12, 3, 7, 1, 13, 14, 3, 15, 16, 5, 17, 3, 5, 18, 8, 3, 7, 2,\n",
|
||
" 1, 13, 14, 3, 19, 20, 8, 21, 5, 8, 9, 10, 22, 3, 20, 8, 21, 5,\n",
|
||
" 8, 9, 10, 3, 23, 3, 4, 18, 17, 9, 5, 23, 10, 8, 2, 2, 8, 9,\n",
|
||
" 10, 24, 3, 0, 1, 2, 2, 3, 4, 5, 9, 8, 8, 5, 25, 10, 3, 26,\n",
|
||
" 12, 27, 16, 26, 2, 27, 16, 28, 29, 30, 1, 16, 26, 3, 17, 31, 3, 21,\n",
|
||
" 2, 5, 9, 1, 23, 13, 32, 16, 27, 13, 10, 24, 3, 1, 9, 8, 3, 10,\n",
|
||
" 8, 8, 27, 16, 28, 3, 28, 9, 8, 8, 16, 3, 1, 28, 1, 27, 16, 6])"
|
||
]
|
||
},
|
||
"execution_count": 3,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"def enc(x):\n",
|
||
" return torch.LongTensor(encode(x,voc=vocab,tokenizer=char_tokenizer))\n",
|
||
"\n",
|
||
"enc(train_dataset[0][1])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Обучение генеративной RNN\n",
|
||
"\n",
|
||
"Метод, который мы будем использовать для обучения RNN генерации текста, следующий. На каждом шаге мы берем последовательность символов длиной `nchars` и просим сеть сгенерировать следующий выходной символ для каждого входного символа:\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"В зависимости от конкретного сценария, мы также можем включить специальные символы, такие как *конец последовательности* `<eos>`. В нашем случае мы хотим обучить сеть бесконечной генерации текста, поэтому фиксируем размер каждой последовательности равным `nchars` токенов. Таким образом, каждый обучающий пример будет состоять из `nchars` входов и `nchars` выходов (входная последовательность, сдвинутая на один символ влево). Минипакет будет состоять из нескольких таких последовательностей.\n",
|
||
"\n",
|
||
"Метод генерации минипакетов заключается в том, чтобы взять каждый текст новости длиной `l` и создать из него все возможные комбинации входов и выходов (таких комбинаций будет `l-nchars`). Они будут формировать один минипакет, и размер минипакетов будет различаться на каждом этапе обучения.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(tensor([[ 0, 1, 2, ..., 28, 29, 30],\n",
|
||
" [ 1, 2, 2, ..., 29, 30, 1],\n",
|
||
" [ 2, 2, 3, ..., 30, 1, 16],\n",
|
||
" ...,\n",
|
||
" [20, 8, 21, ..., 1, 28, 1],\n",
|
||
" [ 8, 21, 5, ..., 28, 1, 27],\n",
|
||
" [21, 5, 8, ..., 1, 27, 16]]),\n",
|
||
" tensor([[ 1, 2, 2, ..., 29, 30, 1],\n",
|
||
" [ 2, 2, 3, ..., 30, 1, 16],\n",
|
||
" [ 2, 3, 4, ..., 1, 16, 26],\n",
|
||
" ...,\n",
|
||
" [ 8, 21, 5, ..., 28, 1, 27],\n",
|
||
" [21, 5, 8, ..., 1, 27, 16],\n",
|
||
" [ 5, 8, 9, ..., 27, 16, 6]]))"
|
||
]
|
||
},
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"nchars = 100\n",
|
||
"\n",
|
||
"def get_batch(s,nchars=nchars):\n",
|
||
" ins = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
|
||
" outs = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
|
||
" for i in range(len(s)-nchars):\n",
|
||
" ins[i] = enc(s[i:i+nchars])\n",
|
||
" outs[i] = enc(s[i+1:i+nchars+1])\n",
|
||
" return ins,outs\n",
|
||
"\n",
|
||
"get_batch(train_dataset[0][1])"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Теперь давайте определим генераторную сеть. Она может быть основана на любой рекуррентной ячейке, которую мы обсуждали в предыдущем разделе (простая, LSTM или GRU). В нашем примере мы будем использовать LSTM.\n",
|
||
"\n",
|
||
"Поскольку сеть принимает символы в качестве входных данных, а размер словаря довольно небольшой, нам не нужен слой эмбеддинга — вход, закодированный в формате one-hot, может напрямую поступать в ячейку LSTM. Однако, так как мы передаем номера символов в качестве входных данных, их необходимо закодировать в формате one-hot перед передачей в LSTM. Это выполняется вызовом функции `one_hot` во время прохода `forward`. Кодировщик выхода будет представлен линейным слоем, который преобразует скрытое состояние в выход, закодированный в формате one-hot.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class LSTMGenerator(torch.nn.Module):\n",
|
||
" def __init__(self, vocab_size, hidden_dim):\n",
|
||
" super().__init__()\n",
|
||
" self.rnn = torch.nn.LSTM(vocab_size,hidden_dim,batch_first=True)\n",
|
||
" self.fc = torch.nn.Linear(hidden_dim, vocab_size)\n",
|
||
"\n",
|
||
" def forward(self, x, s=None):\n",
|
||
" x = torch.nn.functional.one_hot(x,vocab_size).to(torch.float32)\n",
|
||
" x,s = self.rnn(x,s)\n",
|
||
" return self.fc(x),s"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Во время обучения мы хотим иметь возможность выбирать сгенерированный текст. Для этого мы определим функцию `generate`, которая будет создавать выходную строку длиной `size`, начиная с начальной строки `start`.\n",
|
||
"\n",
|
||
"Как это работает: сначала мы пропускаем всю начальную строку через сеть, получаем выходное состояние `s` и следующий предсказанный символ `out`. Поскольку `out` закодирован в формате one-hot, мы используем `argmax`, чтобы получить индекс символа `nc` в словаре, а затем с помощью `itos` определяем фактический символ и добавляем его в результирующий список символов `chars`. Этот процесс генерации одного символа повторяется `size` раз, чтобы сгенерировать необходимое количество символов.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def generate(net,size=100,start='today '):\n",
|
||
" chars = list(start)\n",
|
||
" out, s = net(enc(chars).view(1,-1).to(device))\n",
|
||
" for i in range(size):\n",
|
||
" nc = torch.argmax(out[0][-1])\n",
|
||
" chars.append(vocab.get_itos()[nc])\n",
|
||
" out, s = net(nc.view(1,-1),s)\n",
|
||
" return ''.join(chars)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Теперь приступим к обучению! Цикл обучения практически такой же, как и во всех наших предыдущих примерах, но вместо точности мы выводим сгенерированный текст каждые 1000 эпох.\n",
|
||
"\n",
|
||
"Особое внимание следует уделить способу вычисления функции потерь. Нам нужно вычислить функцию потерь, используя одноразрядный закодированный вывод `out` и ожидаемый текст `text_out`, который представляет собой список индексов символов. К счастью, функция `cross_entropy` ожидает ненормализованный вывод сети в качестве первого аргумента и номер класса в качестве второго, что как раз соответствует нашим данным. Она также автоматически выполняет усреднение по размеру минипакета.\n",
|
||
"\n",
|
||
"Мы также ограничиваем обучение количеством образцов, заданным в `samples_to_train`, чтобы не ждать слишком долго. Мы рекомендуем вам экспериментировать и попробовать более длительное обучение, возможно, на протяжении нескольких эпох (в этом случае вам нужно будет создать дополнительный цикл вокруг этого кода).\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Current loss = 4.398899078369141\n",
|
||
"today sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr s\n",
|
||
"Current loss = 2.161320447921753\n",
|
||
"today and to the tor to to the tor to to the tor to to the tor to to the tor to to the tor to to the tor t\n",
|
||
"Current loss = 1.6722588539123535\n",
|
||
"today and the court to the could to the could to the could to the could to the could to the could to the c\n",
|
||
"Current loss = 2.423795223236084\n",
|
||
"today and a second to the conternation of the conternation of the conternation of the conternation of the \n",
|
||
"Current loss = 1.702607274055481\n",
|
||
"today and the company to the company to the company to the company to the company to the company to the co\n",
|
||
"Current loss = 1.692358136177063\n",
|
||
"today and the company to the company to the company to the company to the company to the company to the co\n",
|
||
"Current loss = 1.9722288846969604\n",
|
||
"today and the control the control the control the control the control the control the control the control \n",
|
||
"Current loss = 1.8705692291259766\n",
|
||
"today and the second to the second to the second to the second to the second to the second to the second t\n",
|
||
"Current loss = 1.7626899480819702\n",
|
||
"today and a security and a security and a security and a security and a security and a security and a secu\n",
|
||
"Current loss = 1.5574463605880737\n",
|
||
"today and the company and the company and the company and the company and the company and the company and \n",
|
||
"Current loss = 1.5620026588439941\n",
|
||
"today and the be that the be the be that the be the be that the be the be that the be the be that the be t\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"net = LSTMGenerator(vocab_size,64).to(device)\n",
|
||
"\n",
|
||
"samples_to_train = 10000\n",
|
||
"optimizer = torch.optim.Adam(net.parameters(),0.01)\n",
|
||
"loss_fn = torch.nn.CrossEntropyLoss()\n",
|
||
"net.train()\n",
|
||
"for i,x in enumerate(train_dataset):\n",
|
||
" # x[0] is class label, x[1] is text\n",
|
||
" if len(x[1])-nchars<10:\n",
|
||
" continue\n",
|
||
" samples_to_train-=1\n",
|
||
" if not samples_to_train: break\n",
|
||
" text_in, text_out = get_batch(x[1])\n",
|
||
" optimizer.zero_grad()\n",
|
||
" out,s = net(text_in)\n",
|
||
" loss = torch.nn.functional.cross_entropy(out.view(-1,vocab_size),text_out.flatten()) #cross_entropy(out,labels)\n",
|
||
" loss.backward()\n",
|
||
" optimizer.step()\n",
|
||
" if i%1000==0:\n",
|
||
" print(f\"Current loss = {loss.item()}\")\n",
|
||
" print(generate(net))"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Этот пример уже генерирует довольно хороший текст, но его можно улучшить несколькими способами:\n",
|
||
"* **Улучшенная генерация минибатчей**. Способ, которым мы подготовили данные для обучения, заключался в создании одного минибатча из одного образца. Это не идеально, потому что минибатчи получаются разного размера, и некоторые из них вообще невозможно сгенерировать, если текст меньше, чем `nchars`. Кроме того, маленькие минибатчи недостаточно загружают GPU. Было бы разумнее взять один большой фрагмент текста из всех образцов, затем сгенерировать все пары вход-выход, перемешать их и создать минибатчи одинакового размера.\n",
|
||
"* **Многослойный LSTM**. Имеет смысл попробовать 2 или 3 слоя LSTM-ячейк. Как мы упоминали в предыдущем разделе, каждый слой LSTM извлекает определенные шаблоны из текста, и в случае генератора на уровне символов можно ожидать, что нижний уровень LSTM будет отвечать за извлечение слогов, а более высокие уровни — за слова и их комбинации. Это можно легко реализовать, передав параметр количества слоев в конструктор LSTM.\n",
|
||
"* Вы также можете поэкспериментировать с **GRU-ячейками** и посмотреть, какие из них работают лучше, а также с **разными размерами скрытых слоев**. Слишком большой скрытый слой может привести к переобучению (например, сеть будет запоминать точный текст), а слишком маленький размер может не дать хорошего результата.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Генерация мягкого текста и температура\n",
|
||
"\n",
|
||
"В предыдущем определении функции `generate` мы всегда выбирали символ с наибольшей вероятностью в качестве следующего символа в генерируемом тексте. Это приводило к тому, что текст часто \"зацикливался\" на одних и тех же последовательностях символов снова и снова, как в этом примере:\n",
|
||
"```\n",
|
||
"today of the second the company and a second the company ...\n",
|
||
"```\n",
|
||
"\n",
|
||
"Однако, если мы посмотрим на распределение вероятностей для следующего символа, может оказаться, что разница между несколькими наивысшими вероятностями незначительна, например, один символ может иметь вероятность 0.2, а другой — 0.19 и т.д. Например, при поиске следующего символа в последовательности '*play*', следующим символом с равной вероятностью может быть пробел или **e** (как в слове *player*).\n",
|
||
"\n",
|
||
"Это приводит нас к выводу, что не всегда \"справедливо\" выбирать символ с наивысшей вероятностью, так как выбор второго по величине вероятности также может привести к осмысленному тексту. Более разумным подходом будет **выбор** символов из распределения вероятностей, предоставленного выходом сети.\n",
|
||
"\n",
|
||
"Этот выбор можно осуществить с помощью функции `multinomial`, которая реализует так называемое **мультиномиальное распределение**. Функция, реализующая эту **мягкую** генерацию текста, определена ниже:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {
|
||
"scrolled": true
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"--- Temperature = 0.3\n",
|
||
"Today and a company and complete an all the land the restrational the as a security and has provers the pay to and a report and the computer in the stand has filities and working the law the stations for a company and with the company and the final the first company and refight of the state and and workin\n",
|
||
"\n",
|
||
"--- Temperature = 0.8\n",
|
||
"Today he oniis its first to Aus bomblaties the marmation a to manan boogot that pirate assaid a relaid their that goverfin the the Cappets Ecrotional Assonia Cition targets it annight the w scyments Blamity #39;s TVeer Diercheg Reserals fran envyuil that of ster said access what succers of Dour-provelith\n",
|
||
"\n",
|
||
"--- Temperature = 1.0\n",
|
||
"Today holy they a 11 will meda a toket subsuaties, engins for Chanos, they's has stainger past to opening orital his thempting new Nattona was al innerforder advan-than #36;s night year his religuled talitatian what the but with Wednesday to Justment will wemen of Mark CCC Camp as Timed Nae wome a leaders\n",
|
||
"\n",
|
||
"--- Temperature = 1.3\n",
|
||
"Today gpone 2.5 fech atcusion poor cocles toparsdorM.cht Line Pamage put 43 his calt lowed to the book, that has authh-the silia rruch ailing to'ory andhes beutirsimi- Aefffive heading offil an auf eacklets is charged evis, Gunymy oy) Mony has it after-sloythyor loveId out filme, the Natabl -Najuntaxiggs \n",
|
||
"\n",
|
||
"--- Temperature = 1.8\n",
|
||
"Today plary, P.slan chly\\401 mardregationly #39;t 8.1Mide) closes ,filtcon alfly playin roven!\\grea.-QFBEP: Iss onfarchQ/itilia CCf Zivesigntwasta orce.-Peul-aw.uicrin of fuglinfsut aftaningwo, MIEX awayew Aice Woiduar Corvagiugge oppo esig ThusBratourid canthly-RyI.co lagitems\\eexciaishes.conBabntusmor I\n",
|
||
"\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"def generate_soft(net,size=100,start='today ',temperature=1.0):\n",
|
||
" chars = list(start)\n",
|
||
" out, s = net(enc(chars).view(1,-1).to(device))\n",
|
||
" for i in range(size):\n",
|
||
" #nc = torch.argmax(out[0][-1])\n",
|
||
" out_dist = out[0][-1].div(temperature).exp()\n",
|
||
" nc = torch.multinomial(out_dist,1)[0]\n",
|
||
" chars.append(vocab.get_itos()[nc])\n",
|
||
" out, s = net(nc.view(1,-1),s)\n",
|
||
" return ''.join(chars)\n",
|
||
" \n",
|
||
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
|
||
" print(f\"--- Temperature = {i}\\n{generate_soft(net,size=300,start='Today ',temperature=i)}\\n\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"Мы ввели еще один параметр, называемый **temperature**, который используется для указания того, насколько строго мы должны придерживаться наивысшей вероятности. Если temperature равен 1.0, мы выполняем честное мультиномиальное выборочное моделирование, а когда temperature стремится к бесконечности, все вероятности становятся равными, и мы случайным образом выбираем следующий символ. В приведенном ниже примере можно заметить, что текст становится бессмысленным, когда мы слишком сильно увеличиваем temperature, и он напоминает \"циклический\" жестко сгенерированный текст, когда значение приближается к 0.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**Отказ от ответственности**: \nЭтот документ был переведен с использованием сервиса автоматического перевода [Co-op Translator](https://github.com/Azure/co-op-translator). Хотя мы стремимся к точности, пожалуйста, имейте в виду, что автоматические переводы могут содержать ошибки или неточности. Оригинальный документ на его исходном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется профессиональный перевод человеком. Мы не несем ответственности за любые недоразумения или неправильные толкования, возникшие в результате использования данного перевода.\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"interpreter": {
|
||
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
||
},
|
||
"kernelspec": {
|
||
"display_name": "Python 3.8.12 ('py38')",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.8.12"
|
||
},
|
||
"coopTranslator": {
|
||
"original_hash": "7673cd150d96c74c6d6011460094efb4",
|
||
"translation_date": "2025-08-28T12:02:45+00:00",
|
||
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.ipynb",
|
||
"language_code": "ru"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
} |