Вступление
Одним из основных усовершенствований недавно выпущенного PyTorch 1.5 является стабильный паритет API интерфейса C ++ с Python ¹. Интерфейс API C ++ хорошо работает с системами с низкой задержкой, многопоточными средами, существующими базами кода C ++, вы можете проверить мотивацию и варианты использования интерфейса C ++ здесь ³. Я хочу познакомиться с API внешнего интерфейса PyTorch C ++, создав небольшой пример. Итак, я взял простой пример двухслойной нейронной сети из Learning PyTorch с примерами ². В оставшейся части этого поста подробно описаны шаги по преобразованию двухуровневой нейронной сети с использованием примера API интерфейса Python для работы с API интерфейса C ++. Полный пример кода с инструкциями по запуску кода подробно описан в репозитории Github ⁴.
Обновление: график измерения производительности добавлен 05.04.2020.
В PyTorch есть три основных компонента: тензоры, модуль автоматического дифференцирования (autograd) и nn (нейронная сеть).
Тензоры
Python - это динамический язык, и ему не нужно объявлять типы данных для переменных. Где как C ++ - это статически типизированный язык с безопасностью типов для создания скомпилированного и оптимизированного кода, который помогает создавать быстрые и требовательные приложения. Итак, первый шаг, который нужно сделать при преобразовании кода Python, - это начать определение типов данных. Современный C ++ также определяет спецификатор типа auto
, который помогает компилятору определить тип во время компиляции.
Начнем с создания случайных входных и выходных данных тензора для обучения нейронной сети отображению x в y, то есть для изучения функции f который определяет y = f (x).
# Python x = torch.randn(N, D_in) y = torch.randn(N, D_out)
Тензоры в C ++ используют оператор разрешения области видимости :: вместо точечной нотации.
// C++ torch::Tensor x = torch::rand({N, D_in}); torch::Tensor y = torch::rand({N, D_out});
Модель двухуровневой нейронной сети
В Python нейронные сети являются производными от многоразового базового модуля torch.nn.Module
. Подмодули автоматически обнаруживаются и регистрируются с помощью оболочек, когда они назначаются в качестве атрибута модуля. Вот простая двухуровневая сеть с использованием интерфейса Python.
import torch # Python neural network model defined as a class class TwoLayerNet(torch.nn.Module): def __init__(self, D_in, H, D_out): super(TwoLayerNet, self).__init__() self.linear1 = torch.nn.Linear(D_in, H) self.linear2 = torch.nn.Linear(H, D_out) def forward(self, x): h_relu = self.linear1(x).clamp(min=0) y_pred = self.linear2(h_relu) return y_pred
Модули нейронной сети реализованы в виде структур на C ++. Есть два способа определить модули, используя либо семантику значений, либо ссылочную семантику. Сначала мы рассмотрим пример создания модуля с семантикой значений.
#include <torch/torch.h> // C++ neural network model defined with value semantics struct TwoLayerNet : torch::nn::Module { // constructor with submodules registered in the initializer list TwoLayerNet(int64_t D_in, int64_t D_out, int64_t H) : linear1(register_module("linear1", torch::nn::Linear(D_in, H))), linear2(register_module("linear2", torch::nn::Linear(H, D_out))) {} torch::Tensor forward(torch::Tensor x) { x = torch::relu(linear1->forward(x)); x = linear2->forward(x); return x; } torch::nn::Linear linear1; torch::nn::Linear linear2; }; // Usage: access the object using dot . operator TwoLayerNet model(D_in, D_out, H); model.to(device); model.forward(x); model.parameters();
Для регистрации подмодулей в C ++ метод register_module()
, определенный в списке инициализаторов конструкторов, позволяет рекурсивно обращаться к параметрам дерева модулей.
/// Registers a submodule with this `Module`. /// /// This method deals with `ModuleHolder`s. /// /// Registering a module makes it available to methods such as /// `modules()`, `clone()` or `to()`. /// /// \rst /// .. code-block:: cpp /// /// MyModule::MyModule() { /// submodule_ = register_module("linear", torch::nn::Linear(3, 4)); /// } /// \endrst template <typename ModuleType> std::shared_ptr<ModuleType> register_module( std::string name, ModuleHolder<ModuleType> module_holder);
Рекомендуется определять модули C ++ с использованием эталонной семантики с std::shared_ptr
API модульного держателя. Макрос TORCH_MODULE(TwoLayerNet)
определяет класс TwoLayerNet
. Этот «сгенерированный» класс фактически является оболочкой над std::shared_ptr<TwoLayerNetImpl>
. Это упрощает кодирование, вместо того, чтобы писать std::make_shared<TwoLayerNetImpl>(1000, 10, 64)
, вы можете писатьTwoLayerNet(1000, 10, 64)
. Ссылочная семантика - это рекомендуемый способ определения модулей с помощью интерфейса API C ++.
// C++ neural network model defined with reference semantics struct TwoLayerNetImpl : torch::nn::Module { // module holders are assigned in the constructor TwoLayerNetImpl(int64_t D_in, int64_t D_out, int64_t H) : linear1(D_in, H), linear2(H, D_out) { register_module("linear1", linear1); register_module("linear2", linear2); } ... // forward() method torch::nn::Linear linear1{nullptr}; //construct an empty holder torch::nn::Linear linear2{nullptr}; //construct an empty holder }; TORCH_MODULE(TwoLayerNet); // Usage: access the object using arrow -> operator TwoLayerNet model(D_in, D_out, H); model->to(device); model->forward(x); model->parameters();
Оптимизатор
Код оптимизатора очень похож на Python и C ++.
# Python SGD optimizer learning_rate = 1e-4 optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, momentum=0.5) # Zero the gradients before running the backward pass optimizer.zero_grad() # Update weights optimizer.step()
In C++:
// C++ SGD optimizer float_t learning_rate = 1e-4; torch::optim::SGD optimizer( model.parameters(), torch::optim::SGDOptions(learning_rate).momentum(0.5)); // Zero the gradients before running the backward pass optimizer.zero_grad() // Update the weights optimizer.step()
2x + улучшение производительности
Я построил график времени обучения как для Python, так и для C ++, варьируя количество эпох от 500 до 5000 на компьютере Ubuntu 18.04 LTS Intel IvyBridge. Я вижу, что обучение модели C ++ происходит в 2 раза быстрее по сравнению с моделью Python.
В заключение, PyTorch поддерживает паритет между интерфейсами Python и C ++. Интерфейс C ++ в большинстве случаев следует дизайну и эргономике интерфейса Python. Код Python и C ++ для этого простого примера нейронной сети представлен в репозитории Github ⁴.