Выполнение нескольких моделей машинного обучения в цепочке

Windows ML поддерживает высокопроизводительные методы загрузки и выполнения цепочек моделей, тщательно оптимизируя применение графического процессора. Цепочки моделей состоят из двух и более моделей, которые выполняются последовательно с передачей выходных данных одной модели на вход следующей модели в цепочке.

Мы опишем эффективное применение цепочек моделей в Windows ML на примере модели ONNX "FNS-Candy Style Transfer". Этот тип модели вы найдете в папке с примером "FNS-Candy Style Transfer" нашего репозитория на GitHub.

Предположим, что мы хотим выполнить цепочку из двух экземпляров одной модели FNS-Candy с именем mosaic.onnx. Код приложения передает изображение в первую модель цепочки, дожидается ее выходных данных и передает преобразованное изображение другому экземпляру FNS-Candy, которая и создает окончательное изображение.

В следующих шагах показано, как выполнить этот процесс в Windows ML.

Примечание

В реальном сценарии вы, скорее всего, примените две разные модели, но наш пример позволяет в общих чертах понять концепции.

  1. Для начала давайте загрузим модель mosaic.onnx, чтобы мы могли ее использовать.
std::wstring filePath = L"path\\to\\mosaic.onnx"; 
LearningModel model = LearningModel::LoadFromFilePath(filePath);
string filePath = "path\\to\\mosaic.onnx";
LearningModel model = LearningModel.LoadFromFilePath(filePath);
  1. Затем мы создадим два одинаковых сеанса на используемом по умолчанию графическом процессоре устройства, указав одну и ту же модель в качестве входного параметра.
LearningModelSession session1(model, LearningModelDevice(LearningModelDeviceKind::DirectX));
LearningModelSession session2(model, LearningModelDevice(LearningModelDeviceKind::DirectX));
LearningModelSession session1 = 
  new LearningModelSession(model, new LearningModelDevice(LearningModelDeviceKind.DirectX));
LearningModelSession session2 = 
  new LearningModelSession(model, new LearningModelDevice(LearningModelDeviceKind.DirectX));

Примечание

Чтобы получить повышение производительности от использования цепочки, нам нужно создать идентичные сеансы графического процессора для всех моделей. Без этого данные будут дополнительно перемещаться из графического процессора в ЦП, что снизит производительность.

  1. Следующие строки кода позволяют создать привязки для каждого сеанса:
LearningModelBinding binding1(session1);
LearningModelBinding binding2(session2);
LearningModelBinding binding1 = new LearningModelBinding(session1);
LearningModelBinding binding2 = new LearningModelBinding(session2);
  1. Теперь мы привяжем входные данные к первой модели. Здесь мы передаем изображение, размещенное в том же пути, что и сама модель. В нашем примере это изображение с именем fish_720.png.
//get the input descriptor
ILearningModelFeatureDescriptor input = model.InputFeatures().GetAt(0);
//load a SoftwareBitmap
hstring imagePath = L"path\\to\\fish_720.png";

// Get the image and bind it to the model's input
try
{
  StorageFile file = StorageFile::GetFileFromPathAsync(imagePath).get();
  IRandomAccessStream stream = file.OpenAsync(FileAccessMode::Read).get();
  BitmapDecoder decoder = BitmapDecoder::CreateAsync(stream).get();
  SoftwareBitmap softwareBitmap = decoder.GetSoftwareBitmapAsync().get();
  VideoFrame videoFrame = VideoFrame::CreateWithSoftwareBitmap(softwareBitmap);
  ImageFeatureValue image = ImageFeatureValue::CreateFromVideoFrame(videoFrame);
  binding1.Bind(input.Name(), image);
}
catch (...)
{
  printf("Failed to load/bind image\n");
}
//get the input descriptor
ILearningModelFeatureDescriptor input = model.InputFeatures[0];
//load a SoftwareBitmap
string imagePath = "path\\to\\fish_720.png";

// Get the image and bind it to the model's input
try
{
    StorageFile file = await StorageFile.GetFileFromPathAsync(imagePath);
    IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
    SoftwareBitmap softwareBitmap = await decoder.GetSoftwareBitmapAsync();
    VideoFrame videoFrame = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap);
    ImageFeatureValue image = ImageFeatureValue.CreateFromVideoFrame(videoFrame);
    binding1.Bind(input.Name, image);
}
catch
{
    Console.WriteLine("Failed to load/bind image");
}
  1. Чтобы следующая модель в цепочке использовала выходные данные из первой модели, нам нужно создать пустой выходной тензор и привязать к нему выходные данные, чтобы получить маркер для связывания.
//get the output descriptor
ILearningModelFeatureDescriptor output = model.OutputFeatures().GetAt(0);
//create an empty output tensor 
std::vector<int64_t> shape = {1, 3, 720, 720};
TensorFloat outputValue = TensorFloat::Create(shape); 
//bind the (empty) output
binding1.Bind(output.Name(), outputValue);
//get the output descriptor
ILearningModelFeatureDescriptor output = model.OutputFeatures[0];
//create an empty output tensor 
List<long> shape = new List<long> { 1, 3, 720, 720 };
TensorFloat outputValue = TensorFloat.Create(shape);
//bind the (empty) output
binding1.Bind(output.Name, outputValue);

Примечание

При привязке выходных данных укажите тип данных TensorFloat. Это предотвратит дополнительное преобразование из формата тензора после оценки первой модели, то есть избавит от дополнительных очередей в графическом процессоре для операций загрузки и привязки второй модели.

  1. Теперь выполните оценку первой модели и создайте привязку между ее выходными данными и входными данными следующей модели:
//run session1 evaluation
session1.EvaluateAsync(binding1, L"");
//bind the output to the next model input
binding2.Bind(input.Name(), outputValue);
//run session2 evaluation
auto session2AsyncOp = session2.EvaluateAsync(binding2, L"");
//run session1 evaluation
await session1.EvaluateAsync(binding1, "");
//bind the output to the next model input
binding2.Bind(input.Name, outputValue);
//run session2 evaluation
LearningModelEvaluationResult results = await session2.EvaluateAsync(binding2, "");
  1. Наконец, давайте получим окончательный результат выполнения обеих моделей с помощью следующей строки кода.
auto finalOutput = session2AsyncOp.get().Outputs().First().Current().Value();
var finalOutput = results.Outputs.First().Value;

Вот и все! Теперь обе модели могут выполняться последовательно, чтобы максимально эффективно использовать ресурсы графического процессора.

Примечание

Используйте следующие ресурсы для получения справки по машинному обучению в Windows:

  • Чтобы задать технические вопросы о машинном обучении в Windows или ответить на них, используйте тег windows-machine-learning в Stack Overflow.
  • Сообщить об ошибке можно в нашем репозитории GitHub.