Clasificación de imágenes basada en el modelo de ejecución de la API C ++ de PyTorch

Introducción a TorchScript

TorchScript es una forma intermedia del modelo PyTorch, que se puede ejecutar en un entorno de alto rendimiento (como C ++).

Un ejemplo sencillo es el siguiente:

import torch
#import torchvision

class MyCell(torch.nn.Module):
    def __init__(self):
        super(MyCell, self).__init__()

    def forward(self, x, h):
        new_h = torch.tanh(x + h)
        return new_h, new_h

my_cell = MyCell()
x = torch.rand(3, 4)
h = torch.rand(3, 4)
print(my_cell(x, h))

 Resultado de salida:

        Basándonos en el ejemplo anterior, torch.nn.Modulecreamos una clase MyCelly definimos un constructor. El constructor aquí solo llama a la superfunción.
super()La función es un método utilizado para llamar a la clase principal (superclase). superSe usa para resolver el problema de la herencia múltiple. No es un problema llamar al método de la clase principal directamente con el nombre de la clase cuando se usa la herencia única, pero si usa la herencia múltiple, implicará varios problemas, como el orden de búsqueda y las llamadas repetidas. . Al mismo tiempo, también definimos una forwardfunción. La forwardentrada de la función aquí son 2 parámetros y devuelve 2 resultados. El contenido real de la función de reenvío no es muy importante, pero es una pseudo unidad RNN, es decir, la escena real de la función se aplica al bucle.

 

Modificamos aún más la MyCellclase anterior , agregamos un self.linearatributo de miembro (una función) sobre la base original y forwardllamamos al miembro en la función. torch.nn.LinearEs un módulo estándar en PyTorch, que completa la combinación anidada de módulos.

import torch


class MyCell(torch.nn.Module):
    def __init__(self):
        super(MyCell, self).__init__()
        self.linear = torch.nn.Linear(4, 4)
 
    def forward(self, x, h):
        new_h = torch.tanh(self.linear(x) + h)
        return new_h, new_h
 
my_cell = MyCell()
x = torch.rand(3, 4)
h = torch.rand(3, 4)
print(my_cell)
print(my_cell(x, h))

 Resultado de salida:

        Al imprimir un módulo, la salida es la jerarquía de subclase del módulo. Por ejemplo, mycellel resultado de la impresión anterior es la linearsubclase y sus parámetros. Al combinar módulos de esta manera, puede crear fácilmente modelos con componentes reutilizables.
Además, se puede ver en los resultados de salida que existen grad_fn. Esta es la información proporcionada por la diferenciación y derivación automática de PyTorch, llamada autograd. En definitiva, el sistema nos permite calcular derivadas mediante procedimientos potencialmente complejos. Este diseño proporciona una gran flexibilidad para la creación de modelos.

        A continuación, utilizamos ejemplos para ilustrar mejor la flexibilidad de la construcción de modelos. Se agregó sobre la base de lo anterior MyDecisionGate, el flujo de control en forma de bucle o instrucción if en este módulo.

import torch

class MyDecisionGate(torch.nn.Module):
  def forward(self, x):
    if x.sum() > 0:
      return x
    else:
      return -x

class MyCell(torch.nn.Module):
    def __init__(self):
        super(MyCell, self).__init__()
        self.dg = MyDecisionGate()
        self.linear = torch.nn.Linear(4, 4)

    def forward(self, x, h):
        new_h = torch.tanh(self.dg(self.linear(x)) + h)
        return new_h, new_h

my_cell = MyCell()
x = torch.rand(3, 4)
h = torch.rand(3, 4)
print(my_cell)
print(my_cell(x, h))

Resultado de salida:

Rastreo

En resumen, dada la naturaleza flexible y dinámica de PyTorch nativo, TorchScript también proporciona herramientas para capturar definiciones de modelos. Uno de los conceptos centrales es 模型追踪(rastreo).

import torch

class MyCell(torch.nn.Module):
    def __init__(self):
        super(MyCell, self).__init__()
        self.linear = torch.nn.Linear(4, 4)

    def forward(self, x, h):
        new_h = torch.tanh(self.linear(x) + h)
        return new_h, new_h

my_cell = MyCell()
x, h = torch.rand(3, 4), torch.rand(3, 4)
traced_cell = torch.jit.trace(my_cell, (x, h))
print(traced_cell)
traced_cell(x, h)

Resultado de salida: 

                    

        Como antes, cree una instancia MyCell, pero esta vez, use el torch.jit.tracemétodo para llamar a Module y luego pase la entrada de muestra de la red. ¿Para qué es exactamente esto? Ha llamado Module, registró las operaciones que ocurrieron mientras Module se estaba ejecutando y creó una torch.jit.ScriptModuleinstancia (una instancia de TracedModule). TorchScript registra su definición en una representación intermedia (o IR), que generalmente se denomina gráfico en el aprendizaje profundo. Podemos .graphvisualizar el gráfico accediendo a las propiedades: 

print(traced_cell.graph)

Resultado de salida: 

Sin embargo, esta es una representación de muy bajo nivel y la mayor parte de la información contenida en el gráfico no es útil para el usuario final. En su lugar, podemos usar .codeatributos para proporcionar código con una explicación de la sintaxis de Python: 

print(traced_cell.code)

Resultado de salida: 

 

Entonces, ¿por qué hacemos todo esto? Hay varias razones:

  1. El código TorchScript se puede llamar en su propio intérprete, que es básicamente un intérprete de Python restringido . El intérprete no adquiere el bloqueo de intérprete global , por lo que se pueden procesar muchas solicitudes simultáneamente en la misma instancia.
  2. Este formato nos permite guardar todo el modelo en disco y cargarlo en otro entorno, como en un servicio escrito en un lenguaje que no sea Python.
  3. TorchScript nos proporciona una representación, a través de TorchScript podemos realizar la optimización del compilador en el código para proporcionar una ejecución más eficiente.
  4. TorchScript puede interactuar con muchos tiempos de ejecución de backend / dispositivos, que requieren una vista más amplia del programa que una sola operación.

Puede ver traced_cellque el resultado de la llamada es el mismo que el resultado de ejecutar directamente el módulo de Python:
Ejecutar:

import torch

class MyCell(torch.nn.Module):
    def __init__(self):
        super(MyCell, self).__init__()
        self.linear = torch.nn.Linear(4, 4)

    def forward(self, x, h):
        new_h = torch.tanh(self.linear(x) + h)
        return new_h, new_h

my_cell = MyCell()
x, h = torch.rand(3, 4), torch.rand(3, 4)
traced_cell = torch.jit.trace(my_cell, (x, h))
print(traced_cell)
traced_cell(x, h)
print(my_cell(x, h))
print(traced_cell(x, h))

Resultado de salida:

Utilice secuencias de comandos para convertir módulos

      Usamos la segunda versión del módulo por traced_cell(x, h)una razón, en lugar de usar una versión de un submódulo con flujo de control. Ilustremos la razón detrás de esto con el siguiente ejemplo.

import torch

class MyDecisionGate(torch.nn.Module):
  def forward(self, x):
    if x.sum() > 0:
      return x
    else:
      return -x

class MyCell(torch.nn.Module):
    def __init__(self, dg):
        super(MyCell, self).__init__()
        self.dg = dg
        self.linear = torch.nn.Linear(4, 4)

    def forward(self, x, h):
        new_h = torch.tanh(self.dg(self.linear(x)) + h)
        return new_h, new_h

my_cell = MyCell(MyDecisionGate())
x, h = torch.rand(3, 4), torch.rand(3, 4)
traced_cell = torch.jit.trace(my_cell, (x, h))
print(traced_cell.code)

 Resultado de salida:

De acuerdo con .codeel resultado, no if-elsehay rastro de la rama que se pueda encontrar . ¿Por qué? TracingHaz exactamente lo que decimos: ejecuta el código, registra lo que sucede y construye uno que pueda hacer esto ScriptModule. Desafortunadamente, durante esta operación, se borra información como el flujo de control.
Entonces, ¿cómo TorchScriptrepresentar fielmente este módulo en? PyTorch proporciona un compilador de secuencias de comandos , que puede dividir directamente el código fuente de Python para convertirlo en TorchScript. MyDecisionGateConvierta el compilador de script de uso mencionado anteriormente

scripted_gate = torch.jit.script(MyDecisionGate())  # 看这里

my_cell = MyCell(scripted_gate)
traced_cell = torch.jit.script(my_cell)  # 看这里
print(traced_cell.code)
这部分我运行出了些问题,可能是软件版本太低了,有时间使用PyTorch版本1.2.0+cu92试一试,先copy一下,有时间再查原因吧。。。。。

resultado de la operación:

def forward(self,
    x: Tensor,
    h: Tensor) -> Tuple[Tensor, Tensor]:
  _0 = self.linear
  _1 = _0.weight
  _2 = _0.bias
  if torch.eq(torch.dim(x), 2):
    _3 = torch.__isnot__(_2, None)
  else:
    _3 = False
  if _3:
    bias = ops.prim.unchecked_unwrap_optional(_2)
    ret = torch.addmm(bias, x, torch.t(_1), beta=1, alpha=1)
  else:
    output = torch.matmul(x, torch.t(_1))
    if torch.__isnot__(_2, None):
      bias0 = ops.prim.unchecked_unwrap_optional(_2)
      output0 = torch.add_(output, bias0, alpha=1)
    else:
      output0 = output
    ret = output0
  _4 = torch.gt(torch.sum(ret, dtype=None), 0)
  if bool(_4):
    _5 = ret
  else:
    _5 = torch.neg(ret)
  new_h = torch.tanh(torch.add(_5, h, alpha=1))
  return (new_h, new_h)

Ahora, podemos capturar fielmente el comportamiento del programa en TorchScript. Ahora intente ejecutar el programa:

# New inputs
x, h = torch.rand(3, 4), torch.rand(3, 4)
print(traced_cell(x, h))

resultado de la operación: 

(tensor([[ 0.3430, -0.3471,  0.7990,  0.8313],
        [-0.4042, -0.3058,  0.7758,  0.8332],
        [-0.3002, -0.3926,  0.8468,  0.7715]],
       grad_fn=<DifferentiableGraphBackward>), tensor([[ 0.3430, -0.3471,  0.7990,  0.8313],
        [-0.4042, -0.3058,  0.7758,  0.8332],
        [-0.3002, -0.3926,  0.8468,  0.7715]],
       grad_fn=<DifferentiableGraphBackward>))

Tenga en cuenta que la versión PyTorch de este experimento es la 1.1.0+cu9.0。建议使用versión PyTorch1.2.0+cu92。

Scripting y rastreo mixtos

Para ser agregado. . . . . . . . . . . . .

Cargar modelo TorchScript en C ++

Paso 1: Convierta el modelo de PyTorch en Torch Script

El modelo de PyTorch debe Torch Scriptimplementarse de Python a C ++ . Torch Script es una representación del modelo PyTorch, que puede ser entendido, compilado y serializado por el compilador Torch Script. Si escribe un modelo de PyTorch con la API "ansiosa" ordinaria, primero debe convertir el modelo a Torch Script.

Los capítulos anteriores han introducido 2 formas de convertir el modelo PyTorch a Torch Script. El primero es el rastreo, que evalúa la estructura del modelo a través de entradas de instancia y registra el flujo de estas entradas a través del modelo. Este método es adecuado para situaciones en las que el modelo tiene un uso limitado del flujo de control. El segundo método es agregar comentarios explícitos al modelo para que el compilador de Torch Script pueda analizar y compilar directamente el código del modelo. Para obtener información más detallada, consulte la referencia de Torch Script

Rastreando

Para convertir un modelo de PyTorch en Torch Script mediante el seguimiento, la instancia del modelo con entrada de muestra debe ingresarse en la torch.jit.tracefunción. Esto producirá un torch.jit.ScriptModuleobjeto que integra el seguimiento de la evaluación del modelo en el método de reenvío.
Los ejemplos de uso específicos son los siguientes:

import torch
import torchvision

# An instance of your model.
model = torchvision.models.resnet18()

# An example input you would normally provide to your model's forward() method.
example = torch.rand(1, 3, 224, 224)

# Use torch.jit.trace to generate a torch.jit.ScriptModule via tracing.
traced_script_module = torch.jit.trace(model, example)

El ScriptModuleobjeto rastreado ahora se puede considerar como un módulo PyTorch normal.

output = traced_script_module(torch.ones(1, 3, 224, 224))
print(output[0, :5])

Resultado de salida:

tensor([0.7741, 0.0539, 0.6656, 0.7301, 0.2207], grad_fn=<SliceBackward>)

A través de anotación

En algunos casos, por ejemplo, si el modelo adopta una forma específica de flujo de control, puede ser una mejor opción escribir el modelo directamente en Torch Script y etiquetar el modelo en consecuencia. Tome el siguiente modelo de Pytorch como ejemplo:

import torch

class MyModule(torch.nn.Module):
    def __init__(self, N, M):
        super(MyModule, self).__init__()
        self.weight = torch.nn.Parameter(torch.rand(N, M))

    def forward(self, input):
        if input.sum() > 0:
          output = self.weight.mv(input)
        else:
          output = self.weight + input
        return output
因为这个模块中的forward方法使用依赖于输入的控制流依,这种模块不适合于追踪方法。相反,可以将其转换为ScriptModule。为了将模块转换为ScriptModule,需要用torch.jit.script编译模块:
class MyModule(torch.nn.Module):
    def __init__(self, N, M):
        super(MyModule, self).__init__()
        self.weight = torch.nn.Parameter(torch.rand(N, M))

    def forward(self, input):
        if input.sum() > 0:
          output = self.weight.mv(input)
        else:
          output = self.weight + input
        return output

my_module = MyModule(10,20)
sm = torch.jit.script(my_module)

Además, para los nn.Modulemétodos que no son necesarios en (porque TorchScript actualmente no admite algunas funciones de Python), puede usarlos @torch.jit.ignorepara eliminarlos.

Paso 2: serialice el módulo de secuencia de comandos en el archivo

Para el ScriptModuleobjeto obtenido (ya sea mediante el método de seguimiento o el método de anotación), se puede serializar en un archivo para su uso posterior en otros entornos (como C ++). El método de serialización específico es el siguiente:

traced_script_module.save("traced_resnet_model.pt")
  • Si desea serializar el módulo al mismo tiempo my_module, puede usarlo my_module.save("my_module_model.pt").

Paso 3: Cargue el módulo Torch Script en C ++

La carga del modelo de PyTorch serializado en C ++ requiere la API de PyTorch C ++, que es una LibTorchbiblioteca. LibTorchHay bibliotecas compartidas, archivos de encabezado y archivos de configuración de compilación de CMake.

La aplicación C ++ más simplificada

example-app.cppLos contenidos son los siguientes:

#include <torch/script.h> // One-stop header.

#include <iostream>
#include <memory>

int main(int argc, const char* argv[]) {
  if (argc != 2) {
    std::cerr << "usage: example-app <path-to-exported-script-module>\n";
    return -1;
  }


  torch::jit::script::Module module;
  try {
    // Deserialize the ScriptModule from a file using torch::jit::load().
    module = torch::jit::load(argv[1]);
  }
  catch (const c10::Error& e) {
    std::cerr << "error loading the model\n";
    return -1;
  }

  std::cout << "ok\n";
}

El archivo de encabezado <torch/script.h>incluye todas las dependencias de la biblioteca LibTorch necesarias para ejecutar el ejemplo. El ejemplo anterior recibe el ScriptModulearchivo serializado y torch::jit::load()carga el archivo serializado , y el resultado es un torch::jit::script::Moduleobjeto.

Construye dependencias y crea

El contenido de CMakeLists.txt correspondiente al código anterior es el siguiente:

cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(custom_ops)

find_package(Torch REQUIRED)

add_executable(example-app example-app.cpp)
target_link_libraries(example-app "${TORCH_LIBRARIES}")
set_property(TARGET example-app PROPERTY CXX_STANDARD 11)

Descarga libtorch del oficial y descomprímelo:

El libdirectorio contiene las bibliotecas compartidas necesarias para la vinculación; includecontiene los archivos de encabezado utilizados en el programa; el sharedirectorio contiene la configuración de CMake necesaria para facilitar find_package(Torch)el uso de los comandos anteriores .

Por último, debe compilar la aplicación. Suponga que el diseño del directorio es el siguiente:

example-app/
  CMakeLists.txt
  example-app.cpp

Puede ejecutar el siguiente comando para example-app/construir la aplicación desde la carpeta:

mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=/home/data1/devtools/libtorch/ ..
make

Este DCMAKE_PREFIX_PATHvalor es libtorchla ubicación para descomprimir después de la descarga .
Después de la compilación, el modo de funcionamiento es el siguiente:

./example-app <path_to_model>/traced_resnet_model.pt

Paso 4: ejecutar el módulo de secuencia de comandos en C ++ 

La introducción anterior ha podido cargar ResNet18 serializado en C ++, y ahora lo que hay que hacer es ejecutar el modelo para razonar. detalles como sigue:

// Create a vector of inputs.
std::vector<torch::jit::IValue> inputs;
inputs.push_back(torch::ones({1, 3, 224, 224}));

// Execute the model and turn its output into a tensor.
at::Tensor output = module.forward(inputs).toTensor();
std::cout << output.slice(/*dim=*/1, /*start=*/0, /*end=*/5) << '\n';

Las primeras 2 líneas del código anterior son la entrada del modelo, y luego llaman script::Moduleal forwardmétodo, el tipo de resultado devuelto es IValueque necesita toTensor()convertirse más a tensor.

Nota: Si desea ejecutar el modelo con la GPU, sólo tiene que lidiar con el modelo de la siguiente manera: model.to(at::kCUDA);. Al mismo tiempo, es necesario asegurarse de que la entrada del modelo también esté en la memoria CUDA, lo que se puede implementar de las siguientes formas: tensor.to(at::kCUDA)se devolverá un nuevo tensor ubicado en la memoria CUDA.

Ejemplo de clasificación de imágenes

Preparación ambiental

Debe instalar cmake, opencv, PyTroch 1.2 por adelantado. Durante el proceso de instalación de opencv, puede haber algunos problemas de instalación ambientales, como que la versión gcc (gcc5.2 utilizada en este artículo) es demasiado baja, y lo explicaré aquí.

Cargar modelo en C ++

Tome el modelo resnet18 para la clasificación de imágenes como ejemplo.

Paso 1: Convierta el modelo de PyTorch en Torch Script

Ejecute el siguiente script:

import torch
import torchvision
from torchvision import transforms
from PIL import Image
from time import time
import numpy as np

# An instance of your model.
model = torchvision.models.resnet18(pretrained=True)
model.eval()

# An example input you would normally provide to your model's forward() method.
example = torch.rand(1, 3, 224, 224)

# Use torch.jit.trace to generate a torch.jit.ScriptModule via tracing.
traced_script_module = torch.jit.trace(model, example)
traced_script_module.save("model.pt")

# evalute time
batch = torch.rand(64, 3, 224, 224)
start = time()
output = traced_script_module(batch)
stop = time()
print(str(stop-start) + "s")

# read image
image = Image.open('dog.png').convert('RGB')
default_transform = transforms.Compose([
        transforms.Resize([224, 224]),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406],
                             std=[0.229, 0.224, 0.225])
      ])
image = default_transform(image)

# forward
output = traced_script_module(image.unsqueeze(0))
print(output[0, :10])

# print top-5 predicted labels
labels = np.loadtxt('synset_words.txt', dtype=str, delimiter='\n')

data_out = output[0].data.numpy()
sorted_idxs = np.argsort(-data_out)

for i,idx in enumerate(sorted_idxs[:5]):
  print('top-%d label: %s, score: %f' % (i, labels[idx], data_out[idx]))

Obtener model.pt

Paso 2: Llamar a Torch Script en C ++

(1) Primero debe descargar LibTorchy descomprimir , y debe especificar la ruta de la biblioteca al compilar make.
(2) Use la herramienta cmake para compilar el código comercial, es decir, el código usando Torch Script

mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=/home/aaron/WORK/tb1/libtorch/ ..
make

resultado de la operación:

Adjunta el código completo: 

#include "torch/script.h"
#include "torch/torch.h" 
//#include "torch/Tensor.h" 
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgproc/types_c.h"

#include <iostream>
#include <memory>
#include <string>
#include <vector>

/* main */
int main(int argc, const char* argv[]) {
  if (argc < 4) {
    std::cerr << "usage: example-app <path-to-exported-script-module> "
      << "<path-to-image>  <path-to-category-text>\n";
    return -1;
  }

  // Deserialize the ScriptModule from a file using torch::jit::load().
  //std::shared_ptr<torch::jit::script::Module> module = torch::jit::load(argv[1]);
  torch::jit::script::Module module = torch::jit::load(argv[1]);

  //assert(module != nullptr);
  std::cout << "load model ok\n";

  // Create a vector of inputs.
  std::vector<torch::jit::IValue> inputs;
  inputs.push_back(torch::rand({64, 3, 224, 224}));

  // evalute time
  double t = (double)cv::getTickCount();
  module.forward(inputs).toTensor();
  t = (double)cv::getTickCount() - t;
  printf("execution time = %gs\n", t / cv::getTickFrequency());
  inputs.pop_back();

  // load image with opencv and transform
  cv::Mat image;
  image = cv::imread(argv[2], 1);
  cv::cvtColor(image, image, CV_BGR2RGB);
  cv::Mat img_float;
  image.convertTo(img_float, CV_32F, 1.0/255);
  cv::resize(img_float, img_float, cv::Size(224, 224));
  //std::cout << img_float.at<cv::Vec3f>(56,34)[1] << std::endl;
  //auto img_tensor = torch::CPU(torch::kFloat32).tensorFromBlob(img_float.data, {1, 224, 224, 3});

  auto img_tensor = torch::from_blob(img_float.data, {1, 224, 224, 3}); //.permute({0, 3, 1, 2}).to(torch::kCUDA);
 
  img_tensor = img_tensor.permute({0,3,1,2}); //.to(torch::kCUDA);

  img_tensor[0][0] = img_tensor[0][0].sub_(0.485).div_(0.229);
 
  img_tensor[0][1] = img_tensor[0][1].sub_(0.456).div_(0.224);
  img_tensor[0][2] = img_tensor[0][2].sub_(0.406).div_(0.225);

  //auto img_var = torch::autograd::make_variable(img_tensor, false);
  //torch::Tensor img_var = torch::autograd::make_variable(img_tensor, false);
 
  inputs.push_back(img_tensor);
  
  // Execute the model and turn its output into a tensor.
  torch::Tensor out_tensor = module.forward(inputs).toTensor();
 
  std::cout << out_tensor.slice(/*dim=*/1, /*start=*/0, /*end=*/10) << '\n';


  // Load labels
  std::string label_file = argv[3];
  std::ifstream rf(label_file.c_str());
  CHECK(rf) << "Unable to open labels file " << label_file;
  std::string line;
  std::vector<std::string> labels;
  while (std::getline(rf, line))
    labels.push_back(line);

  // print predicted top-5 labels
  std::tuple<torch::Tensor,torch::Tensor> result = out_tensor.sort(-1, true);
  torch::Tensor top_scores = std::get<0>(result)[0];
  torch::Tensor top_idxs = std::get<1>(result)[0].toType(torch::kInt32);
  
  auto top_scores_a = top_scores.accessor<float,1>();
  auto top_idxs_a = top_idxs.accessor<int,1>();

  for (int i = 0; i < 5; ++i) {
    int idx = top_idxs_a[i];
    std::cout << "top-" << i+1 << " label: ";
    std::cout << labels[idx] << ", score: " << top_scores_a[i] << std::endl;
  }

  return 0;
}

Referencia

https://pytorch.org/blog/model-serving-in-pyorch/
https://medium.com/datadriveninvestor/deploy-your-pytorch-model-to-production-f69460192217
https://github.com/iamhankai / cpp-pytorch
https://pytorch.org/tutorials/advanced/cpp_export.html#step-1-converting-your-pytorch-model-to-torch-script

https://blog.csdn.net/jonado13/article/details/108280029

https://blog. 2.control

Supongo que te gusta

Origin blog.csdn.net/wzhrsh/article/details/110436976
Recomendado
Clasificación