Merge pull request 'romanova_adelina_lab_7 is ready' (#290) from romanova_adelina_lab_7 into main
Reviewed-on: http://student.git.athene.tech/Alexey/IIS_2023_1/pulls/290
This commit is contained in:
commit
ea025d0b4a
52
romanova_adelina_lab_7/README.md
Normal file
52
romanova_adelina_lab_7/README.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Лабораторная работа №7. Вариант 21
|
||||
|
||||
## Тема
|
||||
|
||||
Рекуррентная нейронная сеть и задача генерации текста
|
||||
|
||||
## Задание
|
||||
|
||||
- Выбрать художественный текст и обучить на нем рекуррентную нейронную сеть для решения задачи генерации.
|
||||
|
||||
- Подобрать архитектуру и параметры так, чтобы приблизиться к максимально осмысленному результату.
|
||||
|
||||
## Используемые ресурсы
|
||||
|
||||
1. Художественный текст на английском языке ```wonderland.txt```
|
||||
|
||||
2. Python-скрипты: ```generate.py```, ```model.py```, ```train.py```.
|
||||
|
||||
## Описание работы
|
||||
|
||||
### Подготовка данных:
|
||||
В файле ```train.py``` реализована функция ```get_data```, которая загружает художественный текст, приводит его к нижнему регистру, и создает сопоставление символов числовым значениям.
|
||||
|
||||
Текст разбивается на последовательности фиксированной длины ```seq_length```, и каждая последовательность связывается с символом, следующим за ней.
|
||||
|
||||
Данные приводятся к тензорам PyTorch и нормализуются для обучения модели.
|
||||
|
||||
### Архитектура модели:
|
||||
|
||||
В файле ```model.py``` определен класс ```CharModel```, наследуемый от ```nn.Module``` и представляющий собой рекуррентную нейронную сеть.
|
||||
|
||||
Архитектура модели включает в себя один слой LSTM с размером скрытого состояния 256, слой dropout для регуляризации и линейный слой для вывода результатов.
|
||||
|
||||
### Обучение модели:
|
||||
|
||||
В файле ```train.py``` реализован скрипт для обучения модели. Выбрана оптимизация Adam, функция потерь - ```CrossEntropyLoss```.
|
||||
|
||||
Обучение происходит на GPU, если он доступен. Обучение проводится в течение нескольких эпох, с валидацией на каждой эпохе. Сохраняется лучшая модель.
|
||||
|
||||
Процесс обучения модели:
|
||||
|
||||
![](train_process.png "")
|
||||
|
||||
### Генерация текста:
|
||||
|
||||
В файле ```generate.py``` модель загружается из сохраненного состояния. Генерируется случайный промпт из исходного текста, и модель используется для предсказания следующего символа в цикле.
|
||||
|
||||
## Вывод:
|
||||
|
||||
![](generated_text.png "")
|
||||
|
||||
В сгенерированном тексте можно найти осмысленные участки, поэтому можно сделать вывод, что модель действительно хорошо обучилась.
|
46
romanova_adelina_lab_7/generate.py
Normal file
46
romanova_adelina_lab_7/generate.py
Normal file
@ -0,0 +1,46 @@
|
||||
import torch
|
||||
from model import CharModel
|
||||
import numpy as np
|
||||
|
||||
if __name__ == "__main__":
|
||||
best_model, char_to_int = torch.load("single-char.pth")
|
||||
n_vocab = len(char_to_int)
|
||||
int_to_char = dict((i, c) for c, i in char_to_int.items())
|
||||
|
||||
|
||||
model = CharModel()
|
||||
model.load_state_dict(best_model)
|
||||
|
||||
# randomly generate a prompt
|
||||
filename = "wonderland.txt"
|
||||
seq_length = 100
|
||||
raw_text = open(filename, 'r', encoding='utf-8').read()
|
||||
raw_text = raw_text.lower()
|
||||
|
||||
start = np.random.randint(0, len(raw_text)-seq_length)
|
||||
prompt = raw_text[start:start+seq_length]
|
||||
pattern = [char_to_int[c] for c in prompt]
|
||||
|
||||
model.eval()
|
||||
print(f'Prompt:\n{prompt}')
|
||||
print("==="*15, "Сгенерированный результ", "==="*15, sep=" ")
|
||||
|
||||
with torch.no_grad():
|
||||
for i in range(1000):
|
||||
# format input array of int into PyTorch tensor
|
||||
x = np.reshape(pattern, (1, len(pattern), 1)) / float(n_vocab)
|
||||
x = torch.tensor(x, dtype=torch.float32)
|
||||
# generate logits as output from the model
|
||||
prediction = model(x)
|
||||
# convert logits into one character
|
||||
index = int(prediction.argmax())
|
||||
result = int_to_char[index]
|
||||
print(result, end="")
|
||||
# append the new character into the prompt for the next iteration
|
||||
pattern.append(index)
|
||||
pattern = pattern[1:]
|
||||
|
||||
print()
|
||||
print("==="*30)
|
||||
print("Done.")
|
||||
|
BIN
romanova_adelina_lab_7/generated_text.png
Normal file
BIN
romanova_adelina_lab_7/generated_text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
16
romanova_adelina_lab_7/model.py
Normal file
16
romanova_adelina_lab_7/model.py
Normal file
@ -0,0 +1,16 @@
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class CharModel(nn.Module):
|
||||
def __init__(self, n_vocab):
|
||||
super().__init__()
|
||||
self.lstm = nn.LSTM(input_size=1, hidden_size=256, num_layers=1, batch_first=True)
|
||||
self.dropout = nn.Dropout(0.2)
|
||||
self.linear = nn.Linear(256, n_vocab)
|
||||
def forward(self, x):
|
||||
x, _ = self.lstm(x)
|
||||
# take only the last output
|
||||
x = x[:, -1, :]
|
||||
# produce output
|
||||
x = self.linear(self.dropout(x))
|
||||
return x
|
BIN
romanova_adelina_lab_7/single-char.pth
Normal file
BIN
romanova_adelina_lab_7/single-char.pth
Normal file
Binary file not shown.
86
romanova_adelina_lab_7/train.py
Normal file
86
romanova_adelina_lab_7/train.py
Normal file
@ -0,0 +1,86 @@
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
import torch.optim as optim
|
||||
import torch.utils.data as data
|
||||
import torch
|
||||
from model import CharModel
|
||||
|
||||
|
||||
def get_data(filename="wonderland.txt"):
|
||||
# загружаем датасет и приводим к нижнему регистру
|
||||
filename = "wonderland.txt"
|
||||
raw_text = open(filename, 'r', encoding='utf-8').read()
|
||||
raw_text = raw_text.lower()
|
||||
|
||||
# делаем сопоставление текста с соответствующим ему значением
|
||||
chars = sorted(list(set(raw_text)))
|
||||
char_to_int = dict((c, i) for i, c in enumerate(chars))
|
||||
|
||||
# статистика обучаемых данных
|
||||
n_chars = len(raw_text)
|
||||
n_vocab = len(chars)
|
||||
print("Total Characters: ", n_chars)
|
||||
print("Total Vocab: ", n_vocab)
|
||||
|
||||
# подготовка датасета
|
||||
seq_length = 100
|
||||
dataX = []
|
||||
dataY = []
|
||||
for i in range(0, n_chars - seq_length, 1):
|
||||
seq_in = raw_text[i:i + seq_length]
|
||||
seq_out = raw_text[i + seq_length]
|
||||
dataX.append([char_to_int[char] for char in seq_in])
|
||||
dataY.append(char_to_int[seq_out])
|
||||
n_patterns = len(dataX)
|
||||
print("Total Patterns: ", n_patterns)
|
||||
|
||||
# --- переводим данные к тензору, чтобы рабоать с ними внутри pytorch ---
|
||||
X = torch.tensor(dataX, dtype=torch.float32).reshape(n_patterns, seq_length, 1)
|
||||
X = X / float(n_vocab)
|
||||
y = torch.tensor(dataY)
|
||||
print(X.shape, y.shape)
|
||||
|
||||
return X, y, char_to_int
|
||||
|
||||
|
||||
def main():
|
||||
X, y, char_to_int = get_data()
|
||||
|
||||
n_epochs = 40
|
||||
batch_size = 128
|
||||
model = CharModel()
|
||||
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
||||
print(f"device: {device}")
|
||||
model.to(device)
|
||||
|
||||
optimizer = optim.Adam(model.parameters())
|
||||
loss_fn = nn.CrossEntropyLoss(reduction="sum")
|
||||
loader = data.DataLoader(data.TensorDataset(X, y), shuffle=True, batch_size=batch_size)
|
||||
|
||||
best_model = None
|
||||
best_loss = np.inf
|
||||
|
||||
for epoch in range(n_epochs):
|
||||
model.train()
|
||||
for X_batch, y_batch in loader:
|
||||
y_pred = model(X_batch.to(device))
|
||||
loss = loss_fn(y_pred, y_batch.to(device))
|
||||
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# Validation
|
||||
model.eval()
|
||||
loss = 0
|
||||
with torch.no_grad():
|
||||
for X_batch, y_batch in loader:
|
||||
y_pred = model(X_batch.to(device))
|
||||
loss += loss_fn(y_pred, y_batch.to(device))
|
||||
if loss < best_loss:
|
||||
best_loss = loss
|
||||
best_model = model.state_dict()
|
||||
print("Epoch %d: Cross-entropy: %.4f" % (epoch, loss))
|
||||
|
||||
torch.save([best_model, char_to_int], "single-char.pth")
|
||||
|
BIN
romanova_adelina_lab_7/train_process.png
Normal file
BIN
romanova_adelina_lab_7/train_process.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
3375
romanova_adelina_lab_7/wonderland.txt
Normal file
3375
romanova_adelina_lab_7/wonderland.txt
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user