Рекуррентные нейронные сети¶
Рекуррентные нейронные сети работают с информацией, в которой есть последовательность,где текущее значение данных зависит от предыдущего.
В этом туториале мы будем работать с аудио данными, используя LSTM.
Этот ноутбук и датасеты доступны в моем Github репозитории:
git clone https://github.com/andreiliphd/reinforcement-content.git
Если нет Git, то его нужно установить.
Linux:
sudo apt-get update
sudo apt-get install git
Windows: скачайте Git с сайта git-scm.com.
Если вы нашли ошибку на сайте, ее можно исправить самостоятельно сделав Pull Request в Git.
Объяснение принципа работы слоя torch.nn.LSTM¶
$i_t$ - входной затвор контролирует сколько данных выходит из $g_t$ в формуле $c_t$.
$f_t$ - затвор забывания контролирует сколько данных от скрытого состояния идет в следующую по времени клетку $t+1$ в формуле $c_t$.
$g_t$ - затвор клетки выводит обработанные данные.
$o_t$ - выходной затвор - наш output
то, что мы дальнешем используем в нейронной сети.
$c_t$ - состояние клетки определяет сколько данных надо забыть с использованием затвора забывания и затвора клетки.
$h_t$ - скрытое состояние
$W$ и $b$ - это torch.nn.Parameter
другими словами параметры, которые оптимизируются, что позволяет всем затворам работать. На самом деле их четыре два $W$ и два $b$.
Вход:
$x_t$ - входные данные в LSTM
Выходы:
$o_t$ - выходной затвор, output
$c_t$ - состояние клетки, которое податся обратно в следующую клетку во времени $t+1$, h_0
$h_t$ - скрытое состояние, которое податся обратно в следующую клетку во времени $t+1$, c_0
Визуализация¶
Видео¶
Видео, которое объясняет суть формулы и принцип рекуррентных сетей.
from IPython.display import IFrame
IFrame('https://www.youtube.com/embed/RSs9auEznRw', width="560", height="315")
Подготовительный этап¶
Нам нужно установить зависимости прежде чем начать работу. Выполните команды:
conda install -c conda-forge sox
и
conda install -c pytorch torchaudio
Этими командами мы установим torchaudio
, которая является библиотекой от Facebook для работы со звуком.
Этапы решения задачи¶
- Загрузка и аугментация данных.
- Декларирование модели.
- Инстанциирование модели.
- Инстанциирование функции потерь(лосс).
- Инстанциирование оптимизатора.
- Создание тренировочной петли(лупа).
Загрузка и аугментация данных¶
Какие проблемы перед нами стоят:
- В
torchaudio
нет готового класса для загрузки аудио. - Аудио нужно конвертировать в MFCC для того, чтобы извлечь черты.
- Нужно изменить форму данных в нашем загрузчике, чтобы отвечать формату [batch, seq_len, input_size].
- Чтобы использовать такой формат при создании LSTM нужно указать
batch_first=True
.
import glob
import torch
import torchaudio
class VoiceDataset(torch.utils.data.Dataset):
def __init__(self, root):
super(VoiceDataset, self).__init__()
self.root = root
self.files = glob.glob(root + '*/*')
classes, class_to_idx = self._find_classes(self.root)
self.classes = classes
self.class_to_idx = class_to_idx
def __getitem__(self, index):
waveform, sample_rate = torchaudio.load(self.files[index])
sample = torchaudio.transforms.MFCC(sample_rate=sample_rate)(waveform)
sample = sample.reshape(80,442)
target = self.class_to_idx[self.files[index].split('/')[2]]
return sample, target
def __len__(self):
return len(self.files)
# the code below is taken from PyTorch source code DatasetFolder class by @andreiliphd
def _find_classes(self, dir):
if sys.version_info >= (3, 5):
classes = [d.name for d in os.scandir(dir) if d.is_dir()]
else:
classes = [d for d in os.listdir(dir) if os.path.isdir(os.path.join(dir, d))]
classes.sort()
class_to_idx = {classes[i]: i for i in range(len(classes))}
return classes, class_to_idx
Для того, чтобы создать свой датасет, которым потом можно будет пользоваться в загрузчике нужно сделать класс VoiceDataset
наследовать от torch.utils.data.Dataset
, а также необходимо переопределить методы __len__
и __getitem__
. Метод __len__
выдает количество данных(количество наших аудио файлов) и метод __getitem__
по индексу загружает данных с диска. Есть встроенные датасеты для изображений в PyTorch, но в нашем случае пришлось идти длинной дорогой, так как датасет для аудио я не нашел. Такая же логика для любых данных, которые нативно не поддерживает PyTorch.
sound = torch.utils.data.DataLoader(VoiceDataset('datasets/sound/'),batch_size=2, shuffle=True)
Передаем класс VoiceDataset
в даталоадер.
Декларирование модели¶
import torch.nn as nn
import torch.nn.functional as F
class RNN(nn.Module):
def __init__(self):
super(RNN, self).__init__()
self.lstm1 = nn.LSTM(input_size=442, hidden_size=256,num_layers=3, batch_first=True)
self.fc1 = nn.Linear(in_features=20480, out_features=128)
self.fc2 = nn.Linear(in_features=128, out_features=64)
self.fc3 = nn.Linear(in_features=64, out_features=32)
self.fc4 = nn.Linear(in_features=32, out_features=3)
def forward(self, x):
x = torch.tanh(self.lstm1(x)[0])
x = x.view(x.shape[0],-1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.relu(self.fc3(x))
x = self.fc4(x)
return x
Обратите внимание на self.lstm1(x)[0]
, мы не берем скрытое состояние LSTM, так как нам наши данные независимы от друг друга. Каждый батч по отдельности. И каждый батч это один, два или три.
Инстанциирование модели¶
model = RNN()
if torch.cuda.is_available():
model = model.cuda()
И перемещение ее на видеокарту для увеличения скорости обучения.
Инстанциирование функции потерь(лосс)¶
criterion = torch.nn.CrossEntropyLoss()
Стандартный лосс для задач классификации.
Инстанциирование оптимизатора¶
optimizer = torch.optim.Adam(model.parameters(), lr=0.0003)
Создание тренировочной петли(лупа)¶
for epoch in range(10):
model.train()
for data, label in sound:
if torch.cuda.is_available():
data, label = data.cuda(), label.cuda()
output = model(data)
loss = criterion(output, label)
optimizer.zero_grad()
loss.backward()
optimizer.step()
model.eval()
print('Epoch: ', epoch, 'Loss: ', loss.item())
total_correct = 0
total = 0
for data, target in sound:
if torch.cuda.is_available():
data, target = data.cuda(), target.cuda()
output = model(data)
loss = criterion(output, target)
max_arg_output = torch.argmax(output, dim=1)
total_correct += int(torch.sum(max_arg_output == target))
total += data.shape[0]
print('Training accuracy: {:.0%}'.format(total_correct/total))
Epoch: 0 Loss: 1.065126657485962 Training accuracy: 33% Epoch: 1 Loss: 1.1673775911331177 Training accuracy: 40% Epoch: 2 Loss: 1.0520007610321045 Training accuracy: 67% Epoch: 3 Loss: 0.9513782262802124 Training accuracy: 67% Epoch: 4 Loss: 0.2251937985420227 Training accuracy: 93% Epoch: 5 Loss: 0.4559391736984253 Training accuracy: 100% Epoch: 6 Loss: 0.16064739227294922 Training accuracy: 97% Epoch: 7 Loss: 0.031351327896118164 Training accuracy: 100% Epoch: 8 Loss: 0.0012655258178710938 Training accuracy: 100% Epoch: 9 Loss: 0.00048160552978515625 Training accuracy: 100%
Обучение¶
Рекуррентные сети обладают достаточно большой формулой, но принцип ее работы простой. Также просто делается рекуррентная модель.
Записывайтесь на мой курс, где я расскажу все более детально и подробно, а главное простыми словами. Для меня нет глупых вопросов, для меня нет начинающих, для меня есть желающие познать и я помогаю им в этом.