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.Module
creamos una clase MyCell
y definimos un constructor. El constructor aquí solo llama a la super
función. super()
La función es un método utilizado para llamar a la clase principal (superclase). super
Se 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 forward
función. La forward
entrada 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 MyCell
clase anterior , agregamos un self.linear
atributo de miembro (una función) sobre la base original y forward
llamamos al miembro en la función. torch.nn.Linear
Es 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, mycell
el resultado de la impresión anterior es la linear
subclase 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.trace
mé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.ScriptModule
instancia (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 .graph
visualizar 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 .code
atributos 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:
- 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.
- 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.
- 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.
- 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_cell
que 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 .code
el resultado, no if-else
hay rastro de la rama que se pueda encontrar . ¿Por qué? Tracing
Haz 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 TorchScript
representar 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
. MyDecisionGate
Convierta 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 Script
implementarse 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.trace
función. Esto producirá un torch.jit.ScriptModule
objeto 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 ScriptModule
objeto 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.Module
métodos que no son necesarios en (porque TorchScript actualmente no admite algunas funciones de Python), puede usarlos @torch.jit.ignore
para eliminarlos.
Paso 2: serialice el módulo de secuencia de comandos en el archivo
Para el ScriptModule
objeto 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 usarlomy_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 LibTorch
biblioteca. LibTorch
Hay bibliotecas compartidas, archivos de encabezado y archivos de configuración de compilación de CMake.
La aplicación C ++ más simplificada
example-app.cpp
Los 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 ScriptModule
archivo serializado y torch::jit::load()
carga el archivo serializado , y el resultado es un torch::jit::script::Module
objeto.
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 lib
directorio contiene las bibliotecas compartidas necesarias para la vinculación; include
contiene los archivos de encabezado utilizados en el programa; el share
directorio 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_PATH
valor es libtorch
la 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::Module
al forward
método, el tipo de resultado devuelto es IValue
que 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 LibTorch
y 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