深度学习实践篇 第八章: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

相关文章
|
30天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习的新篇章:从理论到实践的飞跃####
本文深入剖析了深度学习的最新进展,探讨了其背后的理论基础与实际应用之间的桥梁。通过实例展示了深度学习如何革新计算机视觉、自然语言处理等领域,并展望了其未来可能带来的颠覆性变化。文章旨在为读者提供一个清晰的视角,理解深度学习不仅是技术的飞跃,更是推动社会进步的重要力量。 ####
137 61
|
5天前
|
机器学习/深度学习 人工智能 TensorFlow
人工智能浪潮下的自我修养:从Python编程入门到深度学习实践
【10月更文挑战第39天】本文旨在为初学者提供一条清晰的道路,从Python基础语法的掌握到深度学习领域的探索。我们将通过简明扼要的语言和实际代码示例,引导读者逐步构建起对人工智能技术的理解和应用能力。文章不仅涵盖Python编程的基础,还将深入探讨深度学习的核心概念、工具和实战技巧,帮助读者在AI的浪潮中找到自己的位置。
|
1月前
|
机器学习/深度学习 人工智能 TensorFlow
深度学习的探索之旅:从基础到实践
【10月更文挑战第4天】本文将带领读者踏上一段深度学习的探索之旅。我们将从深度学习的基础概念出发,逐步深入到模型构建、训练和优化的实践应用。通过通俗易懂的语言和实际代码示例,本文旨在帮助初学者理解深度学习的核心原理,并鼓励他们动手实践,以加深对这一强大技术的理解和应用。无论你是AI领域的新手还是有一定经验的开发者,这篇文章都将为你提供有价值的见解和指导。
49 5
|
9天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的卷积神经网络:从理论到实践
【10月更文挑战第35天】在人工智能的浪潮中,深度学习技术以其强大的数据处理能力成为科技界的宠儿。其中,卷积神经网络(CNN)作为深度学习的一个重要分支,在图像识别和视频分析等领域展现出了惊人的潜力。本文将深入浅出地介绍CNN的工作原理,并结合实际代码示例,带领读者从零开始构建一个简单的CNN模型,探索其在图像分类任务中的应用。通过本文,读者不仅能够理解CNN背后的数学原理,还能学会如何利用现代深度学习框架实现自己的CNN模型。
|
6天前
|
机器学习/深度学习 数据采集 自然语言处理
深入浅出深度学习:从理论到实践
【10月更文挑战第38天】本文旨在通过浅显易懂的语言和直观的代码示例,带领读者探索深度学习的奥秘。我们将从深度学习的基本概念出发,逐步深入到模型构建、训练以及应用实例,让初学者也能轻松入门。文章不仅介绍了深度学习的原理,还提供了实战操作指南,帮助读者在实践中加深理解。无论你是编程新手还是有一定基础的学习者,都能在这篇文章中找到有价值的内容。让我们一起开启深度学习之旅吧!
|
26天前
|
机器学习/深度学习 调度 计算机视觉
深度学习中的学习率调度:循环学习率、SGDR、1cycle 等方法介绍及实践策略研究
本文探讨了多种学习率调度策略在神经网络训练中的应用,强调了选择合适学习率的重要性。文章介绍了阶梯式衰减、余弦退火、循环学习率等策略,并分析了它们在不同实验设置下的表现。研究表明,循环学习率和SGDR等策略在提高模型性能和加快训练速度方面表现出色,而REX调度则在不同预算条件下表现稳定。这些策略为深度学习实践者提供了实用的指导。
33 2
深度学习中的学习率调度:循环学习率、SGDR、1cycle 等方法介绍及实践策略研究
|
8天前
|
机器学习/深度学习 自然语言处理 语音技术
深度学习的奇妙之旅:从理论到实践
【10月更文挑战第36天】在本文中,我们将一起探索深度学习的神秘世界。我们将首先了解深度学习的基本概念和原理,然后通过一个简单的Python代码示例,学习如何使用深度学习库Keras进行图像分类。无论你是深度学习的初学者,还是有一定基础的学习者,都可以从这篇文章中获得新的知识和启示。
|
13天前
|
机器学习/深度学习 监控 PyTorch
深度学习工程实践:PyTorch Lightning与Ignite框架的技术特性对比分析
在深度学习框架的选择上,PyTorch Lightning和Ignite代表了两种不同的技术路线。本文将从技术实现的角度,深入分析这两个框架在实际应用中的差异,为开发者提供客观的技术参考。
33 7
|
21天前
|
机器学习/深度学习 数据采集 人工智能
深度学习的魔法:从理论到实践的探索####
【10月更文挑战第22天】 本文深入探讨了深度学习这一现代人工智能领域的璀璨明珠,通过生动实例与通俗语言,揭示了其背后的原理、发展历程及在多个行业的应用潜力。文章首先概述了深度学习的基本概念,随后详细解析了神经网络的核心构成,并探讨了当前面临的挑战与未来趋势。最终,通过实际案例展示了深度学习如何改变世界,为读者呈现一幅技术革新引领未来的画卷。 ####
25 3
|
30天前
|
机器学习/深度学习 人工智能 监控
深入理解深度学习中的卷积神经网络(CNN):从原理到实践
【10月更文挑战第14天】深入理解深度学习中的卷积神经网络(CNN):从原理到实践
83 1