Советы по использованию PyTorch для повышения производительности
Введение
Приходилось ли вам часами отлаживать модель машинного обучения, но никак не удавалось найти причину, по которой точность не улучшается? Вам казалось, что все должно работать идеально, но по какой-то загадочной причине вы не получаете хороших результатов?
Больше не надо. Знакомство с PyTorch для начинающих может оказаться нелегким делом. В этой статье мы рассмотрим проверенные фишки, которые, несомненно, улучшат ваши результаты и повысят производительность вашей модели.
1. Масштабирование
Вы когда-нибудь часами обучали модель на большом наборе данных, а потом обнаружили, что потери не уменьшаются, а точность просто падает? Сначала проверьте модель на вменяемость.
Обучение и оценка на большом наборе данных может занять много времени, поэтому проще сначала отладить модель на небольшом датасете. Убедившись в работоспособности модели, можно легко масштабировать обучение на весь набор данных.
Вместо обучения на всем наборе данных всегда проводите обучение на одной части для проверки.
batch = next(iter(train_dataloader)) # Get a single batch
# For all epochs, keep training on the single batch.
for epoch in range(num_epochs):
inputs, targets = batch
predictions = model.train(inputs)
Рассмотрим приведенный выше фрагмент кода. Предположим, что у нас уже есть загрузчик обучающих данных и модель. Вместо того чтобы выполнять итерации по всему набору данных, мы можем легко загрузить часть данных. Затем мы можем провести обучение на этой часть данных, чтобы проверить, может ли модель найти закономерности в данных на этой небольшой части данных.
Если ошибки уменьшаются до очень малого значения, мы знаем, что модель способна перестроиться на этих данных, и можем быть уверены, что она обучается за короткое время. Затем мы можем обучить эту модель на всем наборе данных, просто изменив одну строку следующим образом:
# For all epochs, iterate over all batches of data.
for epoch in range(num_epochs):
for batch in iter(dataloader):
inputs, targets = batch
predictions = model.train(inputs)
Если модель способна работать с частью данных, то она должна быть способна изучить закономерности во всем наборе данных. Такой метод переоценки данных позволяет упростить отладку. Если модель не может обработаь даже одну партию, можно быть уверенным, что проблема заключается в реализации модели, а не в наборе данных.
2. Нормализация и перемешивание данных
Для наборов данных, где последовательность данных не важна, полезно перемешать их. Например, для задач классификации изображений модель будет лучше соответствовать данным, если ей будут передаваться изображения разных классов в одной партии. Передавая данные в одной и той же последовательности, мы рискуем тем, что модель будет изучать закономерности на основе последовательности передаваемых данных, вместо того чтобы изучать внутреннюю дисперсию данных. Поэтому лучше передавать перемешанные данные. Для этого можно просто использовать объект DataLoader, предоставляемый PyTorch, и установить для параметра shuffle значение True.
from torch.utils.data import DataLoader
dataset = # Loading Data
dataloder = DataLoader(dataset, shuffle=True)
Кроме того, при использовании моделей машинного обучения важно нормализовать данные. Это необходимо в тех случаях, когда в данных присутствует большая дисперсия, а конкретный параметр имеет более высокие значения, чем все остальные признаки в наборе данных. Это может привести к тому, что один из параметров будет доминировать над всеми остальными, что приведет к снижению точности. Мы хотим, чтобы все входные параметры находились в одном диапазоне, и лучше, чтобы среднее значение было равно 0, а дисперсия – 1,0. Для этого нам необходимо преобразовать наш набор данных. Зная среднее значение и дисперсию набора данных, мы можем просто воспользоваться функцией torchvision.transforms.Normalize.
import torchvision.transforms as transforms
image_transforms = transforms.Compose([
transforms.ToTensor(),
# Normalize the values in our data
transforms.Normalize(mean=(0.5,), std=(0.5))
])
Мы можем передать в функцию transforms.Normalize среднее и стандартное отклонение по каждому каналу, и она автоматически преобразует данные, имеющие среднее значение 0 и стандартное отклонение 1.
3. Градиентное обрезание
Взрыв градиента – известная проблема в RNN и LSTM. Однако она не ограничивается только этими архитектурами. Любая модель с глубокими слоями может страдать от взрывающихся градиентов. Обратное распространение по высоким градиентам может привести к расхождению вместо постепенного уменьшения потерь.
Рассмотрим приведенный ниже фрагмент кода.
for epoch in range(num_epochs):
for batch in iter(train_dataloader):
inputs, targets = batch
predictions = model(inputs)
optimizer.zero_grad() # Remove all previous gradients
loss = criterion(targets, predictions)
loss.backward() # Computes Gradients for model weights
# Clip the gradients of model weights to a specified max_norm value.
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1)
# Optimize the model weights AFTER CLIPPING
optimizer.step()
Для решения проблемы разрастающегося градиента мы используем метод обрезки градиента, который позволяет обрезать значения градиента в заданном диапазоне. Например, если мы используем 1 в качестве значения обрезки или нормы, то все градиенты будут обрезаны в диапазоне [-1, 1]. Если значение взрывного градиента равно 50, то оно будет обрезано до 1. Таким образом, обрезание градиента решает проблему взрывного градиента, позволяя медленно оптимизировать модель в направлении сходимости.
4. Переключение режима обучения / оценки
Эта единственная строка кода, безусловно, повысит точность тестирования вашей модели. Почти всегда в модели глубокого обучения используются слои выпадения и нормализации. Они необходимы только для стабильного обучения и обеспечения того, чтобы модель не перестраивалась и не расходилась из-за дисперсии данных. Такие слои, как BatchNorm и Dropout, обеспечивают регуляризацию параметров модели в процессе обучения. Однако после обучения они не требуются. При переводе модели в режим оценки отключаются слои, необходимые только для обучения, и для прогнозирования используются полные параметры модели.
Для лучшего понимания рассмотрим фрагмент кода.
for epoch in range(num_epochs):
# Using training Mode when iterating over training dataset
model.train()
for batch in iter(train_dataloader):
# Training Code and Loss Optimization
# Using Evaluation Mode when checking accuarcy on validation dataset
model.eval()
for batch in iter(val_dataloader):
# Only predictions and Loss Calculations. No backpropogation
# No Optimzer Step so we do can omit unrequired layers.
При оценке нам не нужно проводить оптимизацию параметров модели. Мы не вычисляем никаких градиентов на этапах валидации. Для более качественной оценки мы можем отказаться от слоев Dropout и других нормализаторов. Например, включить все параметры модели, а не только подмножество весов, как в слое Dropout. Это существенно повысит точность модели, так как появится возможность использовать полную модель.
5. Использование Module и ModuleList
Модель PyTorch обычно наследуется от базового класса torch.nn.Module. Согласно документации:
Подмодули, назначенные таким образом, будут зарегистрированы, и их параметры также будут преобразованы при вызове to() и т.д.
Что позволяет базовый класс Module, так это регистрировать каждый слой внутри модели. После этого мы можем использовать model.to() и аналогичные функции, такие как model.train() и model.eval(), и они будут применяться к каждому слою модели. Если этого не сделать, то не будет изменено устройство или режим обучения для каждого слоя, содержащегося в модели. Придется делать это вручную. Базовый класс Module автоматически выполнит преобразования, если использовать функцию просто на объекте модели.
Более того, некоторые модели содержат однотипные последовательные слои, которые можно легко инициализировать с помощью цикла for и заключить в список. Это упрощает код. Однако при этом возникает та же проблема, что и выше, поскольку модули в простом списке Python List не регистрируются автоматически в модели. Для содержания подобных последовательных слоев в модели следует использовать ModuleList.
import torch
import torch.nn as nn
# Inherit from the Module Base Class
class Model(nn.Module):
def __init__(self, input_size, output_size):
# Initialize the Module Parent Class
super().__init__()
self.dense_layers = nn.ModuleList()
# Add 5 Linear Layers and contain them within a Modulelist
for i in range(5):
self.dense_layers.append(
nn.Linear(input_size, 512)
)
self.output_layer = nn.Linear(512, output_size)
def forward(self, x):
# Simplifies Foward Propogation.
# Instead of repeating a single line for each layer, use a loop
for layer in range(len(self.dense_layers)):
x = layer(x)
return self.output_layer(x)
Приведенный фрагмент кода демонстрирует правильный способ создания модели и подслоев с моделью. Использование Module и ModuleList позволяет избежать непредвиденных ошибок при обучении и оценке модели.
Заключение
Приведенные выше методы являются лучшими практиками для фреймворка машинного обучения PyTorch. Они широко используются и рекомендуются документацией PyTorch. Использование этих методов должно стать основным способом работы с кодом машинного обучения и, несомненно, улучшит ваши результаты.