深度学习实践篇 第八章:torchscript

简介: 简要介绍torchscript的使用方法。

下文中的代码都使用参考教程中的例子。
会给出一点自己的解释。
参考教程:
mixing-scripting-and-tracing
script and optimize for mobile recipe
https://pytorch.org/docs/stable/jit.html
OPTIMIZING VISION TRANSFORMER MODEL FOR DEPLOYMEN

Introduction

我们训练好并保存的pytorch,支持在python语言下的使用,但是不支持在一些c++语言下使用。为了能让我们的模型在high-performance environment c++环境下使用,我们需要对模型进行格式转换。

好消息!torch本身是有模型格式转换的功能的,所以我们不需要下载额外的包,就可以把它转为能在c++使用的torchscript模型。

复习一下nn.Module()

之前的章节中有讲过,torch中所有模型都是基于nn.Module()这个类,模型的定义都继承了这个类的属性与方法。
一个完整的模型要包括以下三个基本的部分:

  1. 一个构造函数,用于调用模型模块
  2. parameters和sub-modules。它们在构造函数中被初始化,并能在调用中被使用。
  3. forward()函数,决定了模型调用的顺序。

教程中给出了下面一个简单的例子。
例子中定义了一个名为MyCell的类,它继承了torch.nn.Module()的功能。因为这个模型中没有需要训练的参数和网络层,所以先跳过parameters和sub-modules这一步。要注意这里使用了super,调用了父类的构造函数。
在forward()的部分,该方法的传入参数为x和h(忽略了self)。计算过程中只使用了torch.tanh(x+h),这一步没有参数需要更新。返回的结果为new_h。

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

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

接下来对这个小模型进行一些改动,增加一些需要训练的参数。在教程例子中,它给这个模型增加了一个线性层。

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(x,h))

可以看一下我们输出的结果中,多了一个grad_fn,之前我们曾经解释过,这个是反向传播中梯度计算的方法,因为现在有了要学习的参数,所以增加了这个方法。
image.png

pytorch具有很高的灵活性。在教程中提到了重要的一点是,很多框架都会在给出完整定义的情况下再进行求导的计算,而在pytorch中不是的,pytorch会在计算进行的时候记录这个操作,并在求导的过程中replay。所以pytorch时并没有很明确的对这些求导操作做出定义。

我自己也不是太理解这些话。我的个人理解是在backwards过程中tensor的grad_fn是随着当前步更新的,而不是预设好的。下面放出原文。

Many frameworks take the approach of computing symbolic derivatives given a full program representation. However, in PyTorch, we use a gradient tape. We record operations as they occur, and replay them backwards in computing derivatives. In this way, the framework does not have to explicitly define derivatives for all constructs in the language.

Torchscript

torchscript的作用就是根据pytorch code来创建一个模型,这个模型可以在非python环境下被使用。所以在pytorch中训练的模型,能够很容易地被应用到一个非python依赖的生产环境中去。

我们先来看一下代码,熟悉一下其中的方法的作用。

torch.jit.ScriptModule()

ScriptModule()也继承了nn.Module()类,所以它也有很多和nn.Module()一样的方法。比如children(),named_children()等。
它还包括一些神秘的方法。比如:
PROPERTY code 返回forward()函数中代码。这个功能是nn.Module()中没有的。
PROPERTY graph 返回forward()函数中的graph。

torch.jit.script()

torch.jit.script(obj, optimize=None, _frames_up=0, _rcb=None, example_inputs=None)

script() 的作用是检查一个function或者nn.Module()的源码,并把它编译成torchscript code并返回一个ScriptModule或者ScriptFunctions。
TorchScript本身是python language的一个子集,所以它并不能完全支持python中的所有功能,但是一些模型相关的计算它都是支持的。

更详细的介绍可以参考。

https://pytorch.org/docs/stable/jit_language_reference.html#language-reference

里面提到了一些对torchscript的限制,比如函数中的参数类型是不可以发生改变的,在python语言中你可以判断参数的种类并作出对应的操作,在torchscript中这是一个错误操作。torchscript中的参数为做特别说明的情况下,均默认为tensor。

这里的输入可以是一个function也可以是一个nn.Module(),要注意这里的example_inputs是有格式要求的:
(Union[List[Tuple], Dict[Callable, List[Tuple]], None])。

我们对我们定义的MyCell进行script,输入是一个nn.Module(),返回结果是一个ScriptModule()。
image.png

torch.jit.trace(func, example_inputs=None, optimize=None, check_trace=True, check_inputs=None, check_tolerance=1e-05, strict=True, _force_outplace=False, _module_class=None, _compilation_unit=<torch.jit.CompilationUnit object>, example_kwarg_inputs=None, _store_inputs=True)

torch.jit.trace()可以对一个function进行追踪,并返回一个可执行object或者一个ScriptFunction。你必须提供一个example_inputs。

  • The resulting recording of a standalone function produces ScriptFunction.
  • The resulting recording of nn.Module.forward or nn.Module produces ScriptModule.

当传入的是一个普调的function时,如下图,返回的结果是一个scriptfunction。
image.png

不管传入的是nn.Module还是它本身的forward函数,返回的结果都是一样的。
image.png

一些注意事项

trace方法和script方法存在一些区别。比如说trace方法只会跟踪你的输入走过的路径,当你的模型中存在if-else或者别的分支时,trace方法不会记录你的输入没有经过的那些分支。而script方法会分析你的源码,并进行完整的记录。

这样看起来,似乎只要使用script方法就足够了,完全没必须要使用trace方法。接下来我们来看一看使用script方法可能遇到哪些问题。

  • RuntimeError attribute lookup is not defined on python value of type
    当被script的模型接收另一个模型作为参数时,这个模型传入的类型实际上是TracedModule或者ScriptModule。这种情况下,被script的模型无法使用另一个模型中的一些Module格式下可以使用的参数。比如说它想要使用 model2.n_layers,这样就会出现错误,它应该把n_layers作为参数传进去。
  • RuntimeError python value of type ‘…’ cannot be used as a value.
    使用全局变量时会出现这种问题。
  • RuntimeError all inputs of range must be ‘…’, found Tensor (inferred) in argument。
    torchscript函数默认的参数类型都是torch.tensor。当你想使用别的类型时,你需要明确的给出指定。比如
def forward(self, input_seq, input_length, max_length : int):

使用示例

tracing Modules

torchscript提供了一个方法,帮你获取你的模型的完整定义。首先来看一下tracing方法的作用。

使用上方定义的带线性层的小模型。
image.png

来看一下jit.trace做了什么操作,它首先传入了my_cell,然后传入了对应的输入。trace方法会调用这个Module,并且记录其中的每一步操作,并创造一个ScriptModule的实例。

我们可以看一下它的code。
image.png

使用trace方法会有一些天然的缺陷。它追踪了你的输入在function中经过的每一步操作,所以如果你的function中存在判断语句时,未被触发的操作就会被忽略掉。

使用教程中给出的例子。

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())
traced_cell = torch.jit.trace(my_cell, (x, h))

print(traced_cell.dg.code)
print(traced_cell.code)

在这个例子中,MyDecisionGate函数进行了一个判断,假如传入的x的总和大于0,就返回x本身,假如x的总和小于0,就返回-x。

Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!

image.png

我们可以看到因为我们的输入并不能走过if-else的两条路径,所以我们trace的结果中也只有一条路。我们的if-else方法不见了。

scripting Module

在上面的trace方法中,它对你的输入走过的路径进行记录,所以它看不到输入没有经过的地方。而我们的第二个方法,script() 则是直接对你的源码进行分析,所以能够保留比较完整的结果。
image.png

Mixing scripting and tracing

假如你的代码中有些不希望被torch.jit.script记录的常量,你可以使用trace和script的组合,将这些常量隐藏。

对这部分的理解是,对于有多个分支并且又有你想要隐藏的参数的情况下,可以使用trace和script的组合。多分支的部分用script记录,隐藏参数的部分用trace记录。

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

scripted_gate = torch.jit.script(MyDecisionGate())

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

第一个例子,torch.jit.script和traced module内联。

class MyRNNLoop(torch.nn.Module):
    def __init__(self):
        super(MyRNNLoop, self).__init__()
        self.cell = torch.jit.trace(MyCell(scripted_gate), (x, h))

    def forward(self, xs):
        h, y = torch.zeros(3, 4), torch.zeros(3, 4)
        for i in range(xs.size(0)):
            y, h = self.cell(xs[i], h)
        return y, h

rnn_loop = torch.jit.script(MyRNNLoop())
print(rnn_loop.code)

第二个例子,torch.jit.trace()和scripted module内联。

class WrapRNN(torch.nn.Module):
    def __init__(self):
        super(WrapRNN, self).__init__()
        self.loop = torch.jit.script(MyRNNLoop())

    def forward(self, xs):
        y, h = self.loop(xs)
        return torch.relu(y)

traced = torch.jit.trace(WrapRNN(), (torch.rand(10, 3, 4)))
print(traced.code)

我们观察一下第二个例子,比较一下最后使用jit.trace和jit.script有什么区别。
大家可以看到使用trace时,loop的返回结果是_0, y;使用script时,lopp返回的结果是y, h。
image.png

保存和加载模型

torchscript可以将模型独立地保存下来,保存的信息包括模型的code,parameters, attribute和debug information。这些完整的信息让我们的模型可以独立地表达,并在一个完全不同的进程中被加载,下面给出了代码例子。

traced.save('wrapped_rnn.pt')

loaded = torch.jit.load('wrapped_rnn.pt')

print(loaded)
print(loaded.code)

实践与优化

放一下源码的链接OPTIMIZING VISION TRANSFORMER MODEL FOR DEPLOYMENT。链接里内容更详细,有条件的直接看源码。我只是crop出来了中间和torchscript相关的部分。

from PIL import Image
import torch
import timm
import requests
import torchvision.transforms as transforms
from timm.data.constants import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD

print(torch.__version__)
# should be 1.8.0


model = torch.hub.load('facebookresearch/deit:main', 'deit_base_patch16_224', pretrained=True)
model.eval()

transform = transforms.Compose([
    transforms.Resize(256, interpolation=3),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD),
])

img = Image.open(requests.get("https://raw.githubusercontent.com/pytorch/ios-demo-app/master/HelloWorld/HelloWorld/HelloWorld/image.png", stream=True).raw)
ten = transform(img)[None,]
out = model(ten)
clsidx = torch.argmax(out)
print(clsidx.item())

image.png

将模型以script 的形式保存下来

model = torch.hub.load('facebookresearch/deit:main', 'deit_base_patch16_224', pretrained=True)
model.eval()
scripted_model = torch.jit.script(model)
scripted_model.save("fbdeit_scripted.pt")

比较一下两者的时间,两者在时间上是没有什么明显差别的。在教程中使用了一些模型加速的方法,所以inference的时间会变快。
image.png

相关文章
|
1天前
|
机器学习/深度学习 数据采集 自然语言处理
深度学习实践技巧:提升模型性能的详尽指南
深度学习模型在图像分类、自然语言处理、时间序列分析等多个领域都表现出了卓越的性能,但在实际应用中,为了使模型达到最佳效果,常规的标准流程往往不足。本文提供了多种深度学习实践技巧,包括数据预处理、模型设计优化、训练策略和评价与调参等方面的详细操作和代码示例,希望能够为应用实战提供有效的指导和支持。
|
2月前
|
机器学习/深度学习 传感器 数据采集
深度学习在故障检测中的应用:从理论到实践
深度学习在故障检测中的应用:从理论到实践
207 6
|
2月前
|
机器学习/深度学习 存储 人工智能
探索深度学习的奥秘:从理论到实践的技术感悟
本文深入探讨了深度学习技术的核心原理、发展历程以及在实际应用中的体验与挑战。不同于常规摘要,本文旨在通过作者个人的技术实践经历,为读者揭示深度学习领域的复杂性与魅力,同时提供一些实用的技术见解和解决策略。
56 0
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
揭秘人工智能:深度学习的奥秘与实践
在本文中,我们将深入浅出地探索深度学习的神秘面纱。从基础概念到实际应用,你将获得一份简明扼要的指南,助你理解并运用这一前沿技术。我们避开复杂的数学公式和冗长的论述,以直观的方式呈现深度学习的核心原理和应用实例。无论你是技术新手还是有经验的开发者,这篇文章都将为你打开一扇通往人工智能新世界的大门。
|
2月前
|
机器学习/深度学习 算法 TensorFlow
深度学习中的自编码器:从理论到实践
在这篇文章中,我们将深入探讨深度学习的一个重要分支——自编码器。自编码器是一种无监督学习算法,它可以学习数据的有效表示。我们将首先介绍自编码器的基本概念和工作原理,然后通过一个简单的Python代码示例来展示如何实现一个基本的自编码器。最后,我们将讨论自编码器的一些变体,如稀疏自编码器和降噪自编码器,以及它们在实际应用中的优势。
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
揭秘AI:深度学习的奥秘与实践
本文将深入浅出地探讨人工智能中的一个重要分支——深度学习。我们将从基础概念出发,逐步揭示深度学习的原理和工作机制。通过生动的比喻和实际代码示例,本文旨在帮助初学者理解并应用深度学习技术,开启AI之旅。
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
深入浅出深度学习:从理论到实践的探索之旅
在人工智能的璀璨星空中,深度学习如同一颗耀眼的新星,以其强大的数据处理能力引领着技术革新的浪潮。本文将带您走进深度学习的核心概念,揭示其背后的数学原理,并通过实际案例展示如何应用深度学习模型解决现实世界的问题。无论您是初学者还是有一定基础的开发者,这篇文章都将为您提供宝贵的知识和启发。
75 5
|
3月前
|
机器学习/深度学习 自然语言处理 算法
深度学习在图像识别中的应用及实践
本文将探讨深度学习在图像识别领域的应用,并分享一个使用Python和Keras库实现的简单代码示例。我们将介绍深度学习的原理、常用模型以及如何在实际应用中解决问题。通过阅读本文,您将了解深度学习在图像识别中的潜力,并学会如何构建一个简单的神经网络模型。
76 0
|
3月前
|
机器学习/深度学习 自然语言处理 语音技术
深入探索深度学习中的兼容性函数:从原理到实践
深入探索深度学习中的兼容性函数:从原理到实践
50 3
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的卷积神经网络(CNN): 从理论到实践
本文将深入浅出地介绍卷积神经网络(CNN)的工作原理,并带领读者通过一个简单的图像分类项目,实现从理论到代码的转变。我们将探索CNN如何识别和处理图像数据,并通过实例展示如何训练一个有效的CNN模型。无论你是深度学习领域的新手还是希望扩展你的技术栈,这篇文章都将为你提供宝贵的知识和技能。
472 7