前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >TorchScript | 目标检测部署实战

TorchScript | 目标检测部署实战

作者头像
iResearch666
发布于 2023-11-22 06:35:04
发布于 2023-11-22 06:35:04
49800
代码可运行
举报
运行总次数:0
代码可运行

Inference via TorchScript

简介

TorchScript 是可以由 TorchScript 编译器理解、编译和序列化的 PyTorch 模型的表示形式。从根本上说,TorchScript 本身就是一种编程语言。它是使用 PyTorch APIPython 的子集。

TorchScript 软件栈可以将 Python 代码转换成 C++ 代码。TorchScript 软件栈包括两部分:TorchScript(Python)和 LibTorch(C++)。TorchScript 负责将 Python 代码转成一个模型文件,LibTorch 负责解析运行这个模型文件

原理

TorchScript 保存模型有两种模式:trace 模式和 script 模式。

trace 模式

trace 模式就是跟踪模型的执行,然后将其路径记录下来。在使用 trace 模式时,需要构造一个符合要求的输入,然后使用 TorchScript tracer 运行一遍,整个运行过程就会被记录下来。在 trace 模式中运行时,每执行一个算子,就会往当前的 graph 加入一个 node。所有代码执行完毕,每一步的操作就会以一个计算图里的某个节点的形式被保存下来。PyTorch 导出 ONNX 也是使用了这部分代码,所以理论上能够导出 ONNX 的模型也能够使用 trace 模式导出 torch 模型。

trace 模式有比较大的限制:

  • 不能有 if-else 等控制流
  • 只支持 Tensor 操作

为什么有这种限制:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1. 跟踪出的 graph 是静态的,如果有控制流,那么记录下来的只是当时生成模型时走的那条路;
2. 追踪代码是跟 Tensor 算子绑定在一起的,如果是非 Tensor 的操作,是无法被记录的。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Module_0(torch.nn.Module):
    def __init__(self, N, M):
        super(Module_0, self).__init__()
        self.weight = torch.nn.Parameter(torch.rand(N, M))
        self.linear = torch.nn.Linear(N, M)

    def forward(self, input: torch.Tensor) -> torch.Tensor:
        output = self.weight.mm(input)
        output = self.linear(output)
        return output


scripted_module = torch.jit.trace(Module_0(2, 3).eval(), (torch.zeros(3, 2)))
scripted_module.save("Module_0.pt")
script 模式

script 模式不仅支持 if-else 等控制流,还支持非 Tensor 操作,如 List、Tuple、Map 等容器操作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Module_1(torch.nn.Module):
    def __init__(self, N, M):
        super(Module_1, self).__init__()
        self.weight = torch.nn.Parameter(torch.rand(N, M))
        self.linear = torch.nn.Linear(N, M)

    def forward(self, input: torch.Tensor, do_linear: bool) -> torch.Tensor:
        output = self.weight.mm(input)
        if do_linear:
            output = self.linear(output)
        return output


scripted_module = torch.jit.script(Module_1(3, 3).eval())
scripted_module.save("Module_1.pt")

混合模式

一个 module 包含控制流,同时也包含一个只有 Tensor 操作的子模型。这种情况下当然可以直接使用 script 模式,但是 script 模式需要对部分变量进行类型标注。对上述子模型进行 trace,整体再进行 script:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Module_2(torch.nn.Module):
    def __init__(self, N, M):
        super(Module_2, self).__init__()
        self.linear = torch.nn.Linear(N, M)
        self.sub_module = torch.jit.trace(Module_0(2, 3).eval(), (torch.zeros(3, 2)))

    def forward(self, input: torch.Tensor, do_linear: bool) -> torch.Tensor:
        output = self.sub_module(input)
        if do_linear:
            output = self.linear(output)
        return output


scripted_module = torch.jit.script(Module_2(2, 3).eval())

libtorch

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <torch/script.h>

int main() {
  // load module
  torch::jit::script::Module torch_module;
  try {
    torch_module = torch::jit::load("my_module.pt");
  } catch (const c10::Error& e) {
    std::cerr << "error loading the module" << std::endl;
    return -1;
  }

  // make inputs
  std::vector<float> vec(9);
  std::vector<torch::jit::IValue> torch_inputs;
  torch::Tensor torch_tensor =
      torch::from_blob(vec.data(), {3, 3}, torch::kFloat32);
  torch_inputs.emplace_back(torch_tensor);
  torch_inputs.emplace_back(false);

  // run module
  torch::jit::IValue torch_outputs;
  try {
    torch_outputs = torch_module.forward(torch_inputs);
  } catch (const c10::Error& e) {
    std::cerr << "error running the module" << std::endl;
    return -1;
  }

  auto outputs_tensor = torch_outputs.toTensor();
}

语法限制

  • 支持的类型有限,这些类型是指在运行(而非初始化)过程中使用的对象或者函数参数
    • A PyTorch tensor of any dtype, dimension, or backend
    • 这其中不包括 set 数据类型,这意味着需要使用 set 的地方就要通过其他的方式绕过,比如先用 list 然后去重
    • 使用 tuple 时需要声明其中的类型,例如 Tuple[int, int, int],这也就意味着 tuple 在运行时长度不能变化,所以要使用 list 代替
    • 创建字典时,只有 int、float、comple、string、torch.Tensor 可以作为 key
  • 不支持 lambda 函数,但是可以通过自定义排序类的方式实现,略微麻烦,但是可以解决
  • 因为 TorchScript 是静态类型语言,运行时不能变换变量类型
  • 因为编码问题,所以对中文字符串进行遍历时会抛异常,所以尽量不要处理中文,如果需要处理中文,则需要将中文切分成字符粒度后再送入模型中进行处理

部署实战

  1. 首先参考yolov5模型,导出时候模型分为2个部分,一个用trace跟踪的traced_script_module(不包括最后一层),一个是最后检测层self.model.model[-1]
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
model = TracedModel(model, device, opt.img_size)
 
class TracedModel(nn.Module):
 
 def __init__(self, model=None, device=None, img_size=(640,640)): 
  super(TracedModel, self).__init__()
  # model:导入的模型
  # device: cpu、gpu
  # img_size: 输入图像大小
  print(" Convert model to Traced-model... ") 
  self.stride = model.stride # 8., 16., 32
  self.names = model.names # 每个类别的标签名
  self.model = model
 
  self.model = revert_sync_batchnorm(self.model)
  self.model.to('cpu')
  self.model.eval() # 切换为 eval 模式,不计算梯度
 
  self.detect_layer = self.model.model[-1] # 得到最后的检测层
  self.model.traced = True # False 修改为 True
  # 随机制造一个 bs=1 输入 tensor
  rand_example = torch.rand(1, 3, img_size, img_size)
 
  traced_script_module = torch.jit.trace(self.model, rand_example, strict=False)
  #traced_script_module = torch.jit.script(self.model)
  traced_script_module.save("traced_model.pt")
  print(" traced_script_module saved! ")
  self.model = traced_script_module
  self.model.to(device)
  self.detect_layer.to(device)
  print(" model is traced! \n") 
 
 def forward(self, x, augment=False, profile=False):
  out = self.model(x)
  out = self.detect_layer(out)
  return out
  1. 最好将检测层一起导出,model.model[-1].export = True
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
model.model[-1].export = True
img = torch.zeros(opt.batch_size, 3, *opt.img_size).to(device) 
ts = torch.jit.trace(model, img, strict=False)
ts.save(f)
  1. 成功导出torchscript格式后,可用Netro打开,即可验证是否成功

image-20231117175744016

  1. yolov5输出return x if self.training else torch.cat(z, 1)推理输出和训练不同
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class IDetect(nn.Module):
    stride = None  # strides computed during build
    export = False  # onnx export

    def __init__(self, nc=80, anchors=(), ch=()):  # detection layer
        super(IDetect, self).__init__()
        self.nc = nc  # number of classes
        self.no = nc + 5  # number of outputs per anchor
        self.nl = len(anchors)  # number of detection layers
        self.na = len(anchors[0]) // 2  # number of anchors
        self.grid = [torch.zeros(1)] * self.nl  # init grid
        a = torch.tensor(anchors).float().view(self.nl, -1, 2)
        self.register_buffer('anchors', a)  # shape(nl,na,2)
        self.register_buffer('anchor_grid', a.clone().view(self.nl, 1, -1, 1, 1, 2))  # shape(nl,1,na,1,1,2)
        self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)  # output conv
        
        self.ia = nn.ModuleList(ImplicitA(x) for x in ch)
        self.im = nn.ModuleList(ImplicitM(self.no * self.na) for _ in ch)

    def forward(self, x):
        # x = x.copy()  # for profiling
        z = []  # inference output
        self.training |= self.export
        for i in range(self.nl):
            x[i] = self.m[i](self.ia[i](x[i]))  # conv
            x[i] = self.im[i](x[i])
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            # 转ONNX时修改,避免scatterND结点
            # x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
            #替换为:
            x[i] = x[i].view(-1, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

            if not self.training:  # inference
                if self.grid[i].shape[2:4] != x[i].shape[2:4]:
                    self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
                # 转ONNX时修改,避免scatterND结点
                y = x[i].sigmoid()
                # y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xy
                # y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh
                # z.append(y.view(bs, -1, self.no))
                # 替换为:
                xy = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]
                wh = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i].view(1,self.na,1,1,2)
                y = torch.cat((xy,wh,y[..., 4:]),-1)
                z.append(y.view(-1,int(y.size(1) * y.size(2) * y.size(3)) , self.no))
        # 替换掉不必要的return
        # return x if self.training else (torch.cat(z, 1), x)
        #替换为:
        return x if self.training else torch.cat(z, 1)


    @staticmethod
    def _make_grid(nx=20, ny=20):
        yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
        return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
  1. Detect层是YOLOv5最后一层,包含三个输出,分别是下降stride(见stribe属性,8,16,32)倍的网格。

25200=(80∗80+40∗40+20∗20)∗3 按照stride来划分,以640 × 640像素图像为例,stride分别是8,16,32 640 / 8 = 80,这层网格大小是80 × 80 640 / 16 = 40,这层网格大小是40 × 40 640 / 32 = 20,这层网格大小是20 × 20

  1. 后处理代码
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def numpy_sigmoid(x):
    return 1/(1+np.exp(-x))

def make_grid(nx=20, ny=20):
    xv,yv = np.meshgrid(np.arange(nx), np.arange(ny))
    res = np.stack((xv,yv), 2).reshape(1,1,nx,ny,2).astype(np.float32)
    return res
  
def numpy_detect(x, nc=None, bs=1):
    anchor_grid = [10.0, 13.0, 16.0, 30.0, 33.0, 23.0, 30.0, 61.0, 62.0, 45.0, 59.0, 119.0, 116.0, 90.0, 156.0, 198.0, 373.0, 326.0]
    anchor_grid = np.array(anchor_grid).reshape(3,1,-1,1,1,2)
    stride = np.array([8, 16, 32])
    grid = [make_grid(80,80), make_grid(40,40), make_grid(20,20)]
    z = []
    for i in range(3):
        y = numpy_sigmoid(x[i])
        y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + grid[i]) * stride[i]  # xy
        y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * anchor_grid[i]  # wh
        z.append(y.reshape(bs, -1, nc + 5))
    res = np.concatenate(z, 1)
    return res
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
model_script = torch.jit.load('torchscript.pt')
model_script = model_script.to('cuda')
for file in files:
 img_path = os.path.join(test_path,file)
    img0 = cv2.imread(img_path) 
    img = letterbox(img0,new_shape=(640, 640), color=(114, 114, 114), auto=False, scaleFill=False, scaleup=True, stride=32)[0]
    print(f'[img.shape] {img.shape}')
    # 640 448
    # # Convert
    img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
    img = np.ascontiguousarray(img)

    img = torch.from_numpy(img).to(device)
    img = img.half() if half else img.float()  # uint8 to fp16/32
    img /= 255.0  # 0 - 255 to 0.0 - 1.0
    if img.ndimension() == 3:
        img = img.unsqueeze(0)
 
    # ! export torchscript
    pred = model_script(img) 
    pred = [x.data.cpu().numpy() for x in pred]
    pred = numpy_detect(pred, 11)
    pred = torch.tensor(pred).to('cuda')
    
    # Apply NMS
    # (center x, center y, width, height) 
    pred = non_max_suppression(pred, conf_thres=conf_thres, iou_thres=iou_thres, classes=None, agnostic=False)
  • 注意 letterbox 中 auto=False 保持固定尺寸

结果演示

  • 以上仅以yolov5为例,实际并非yolov5

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iResearch666 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Hexo博客集成码云评论系统
    使用码云的issues作为评论系统,就要使用码云来登录评论,所以需要在码云上配置第三方应用。
試毅-思伟
2019/07/26
8850
Hexo博客集成码云评论系统
Gitment评论插件的使用
现在开始添加博客的评论插件Gitment。这里的话我是使用hexo添加gitment插件,如果不是使用hexo,请到官网指定这里。
Johnson木木
2019/08/21
1.3K0
Gitment评论插件的使用
Gitment评论插件的使用
现在开始添加博客的评论插件Gitment。这里的话我是使用hexo添加gitment插件,如果不是使用hexo,请到官网指定这里。
Johnson木木
2019/08/22
7300
Gitment评论插件的使用
Hexo博客集成Gitment评论
Tips:前导必备 Gitment 是一位作者实现的一款基于 GitHub Issues 的评论系统。支持在前端直接引入,不需要任何后端代码。可以在页面进行登录、查看、评论、点赞等操作,同时有完整的 Markdown / GFM 和代码高亮支持。尤为适合各种基于 GitHub Pages 的静态博客或项目页面。 ---- 1、注册 OAuth Application 注册一个新的 OAuth Application ➡️ OAuth Application Application name 应
試毅-思伟
2019/07/26
4880
Hexo博客集成Gitment评论
Hexo系列(2.1) - NexT主题美化与博客功能增强 · 第二章
网上有不少相关的帖子,不过版本会比较旧,而不同版本可能存在代码不同的问题,不过大部分还是大同小异,本系列就不啰嗦重复了,基本只会按照本人所使用的版本以及个人所使用到的内容来进行介绍。
雨临Lewis
2022/01/12
8740
Hexo系列(2.1) - NexT主题美化与博客功能增强 · 第二章
如何在 VitePress 站点中集成 Gitalk 评论插件及其关键注意事项
VitePress 是一个静态站点生成器 (SSG),非常适合用于个人博客或编写技术文档,深受很多开发者的喜爱。不过它缺少一个重要的功能——评论。
陈明勇
2024/09/26
3282
如何在 VitePress 站点中集成 Gitalk 评论插件及其关键注意事项
Gitment评论功能实战 顶
多说关闭了,影响了很多人,正好在v2ex上看到,可以使用GitHub issues来实现博客的评论功能。
用户2146693
2019/08/08
6980
Gitment评论功能实战 
                                                                            顶
comment.js:一个纯JS实现的静态站点评论系统
介绍我用纯JS实现的一个静态站点评论系统,以及实现过程中的心得体会。 前言 我的博客最早是使用 Disqus 来实现评论功能的。Disqus 被墙了之后,改成了多说。今年年初,多说也正式关闭了,于是我被逼着又开始寻找其他的替代评论系统。 我先是试用了网易云跟贴、畅言等几种类似的社会化评论系统。畅言要求站点必须备案,而我实在没有为了评论去申请备案的动力。网易云跟贴的管理后台上有很多不明觉厉的功能,但好像都没多大用处。最致命的问题是我不小心把我的站点绑定到了另一个网易账户,而不是我常用的微博账户。这样的话,我每
HaHack
2018/07/03
2.7K0
第三方登录(2)---GitHub登录
上一篇介绍了如何实现第三方QQ登录,其实都不涉及后端。在前端使用js就可以实现第三方QQ登录。然后如果有数据库操作可以发起ajax请求将登录得到的用户信息发给后端,在后端对用户信息进行保存。第三方登录(1)---qq登录 。今天,我要讲讲第二种第三方登录方式:GitHub登录。很多人已经都听过GitHub这个IT开源平台,而且其实如果我们网站用户主要面向于IT类的,这时候使用GitHub第三方登录就会极度简化用户注册操作。接下来说说如何实现GitHub第三方登录。我们先看看具体流程:
创译科技
2019/06/02
1.8K0
Docusaurus配置Gitalk评论插件
之前使用 vuepress 的时候,使用的评论系统是Valine,可是匿名用户也能直接评论,虽说会过滤垃圾信息,但是后台查看评论与通知总感觉没有那么实在。
愧怍
2022/12/27
7520
Docusaurus配置Gitalk评论插件
hexo-butterfly-评论系统引入
​ 可参考官网提供的评论系统接入方式进行构建,在此过程中也陆陆续续摸索了网友们对各个评论的评价和使用的情况,可结合自身的情况进行调整,从多个方面考虑,不外乎第三方托管应用权限问题、自建服务维护/学习成本、组件引用便捷性等
hahah
2022/06/15
1.9K0
gitalk
在搭建个人博客时,我们经常面临一个小难题:评论系统用什么好? 传统方案如 Disqus 虽然功能强大,但加载慢、隐私问题多;国内的评论系统则可能存在稳定性和接入成本。
阿超
2025/03/28
480
github pages/hexo搭建精致博客
进入一个文件夹(你想把工程放到哪个文件夹就进入哪个文件夹,我的是 ),初始化一个工程
神葳
2021/01/22
4190
VuePress V1 评论插件选型 & 添加 Vssue 评论
为了方便阅读,使用 VuePress 将之前记录的后台常用 Linux 命令博文整理成一个系统的开源在线书籍,希望能够帮到大家。
恋喵大鲤鱼
2023/10/12
5390
VuePress V1 评论插件选型 & 添加 Vssue 评论
Hexo使用Gitalk设置评论区
我一篇衔接用Hexo搭建个人博客网站,主要解决遗留的问题,因为哪一篇太长了,放在一起不好看。
wsuo
2020/07/31
1.1K0
Hexo使用Gitalk设置评论区
为博客添加 Gitalk 评论插件结语
前言 由于 Disqus 对于国内网路的支持十分糟糕,很多人反映 Disqus 评论插件一直加载不出来。而我一直是处于访问外国网站状态的~(话说你们做程序员的都不访问外国网站用Google的吗?,哈哈
BY
2018/05/11
1.4K0
如何用 GitHub Issues 搭建一个轻博客系统:Path Meme 项目实战
一直想随便写点什么东西,但是不想发朋友圈和微博,也没那么多的内容可以写一篇长文章,还想让内容有自主性,不担心随便说话被删,前几天正好试用了一下 Cursor,正好用它按自己的想法写一个可以随便说话的轻博客系统。 Path Meme —— 一个利用 GitHub Issues 作为 CMS 的现代化博客系统。今天,我和大家分享如何从零开始搭建这样一个博客系统。
goodspeed
2024/11/02
1690
Fluid -10- Waline 添加 GitHub 社交登录
官网文档:https://waline.js.org/guide/server/socials.html#github
为为为什么
2022/08/06
5370
Fluid -10- Waline 添加 GitHub 社交登录
为Next主题添加多说评论系统
几个月前,在好奇心的鼓动下,利用Github Pages和Hexo以及Next主题搭建一个属于自己的个人主站,由于时间伧俗,搭建成功后就没有好好完善一下,可以参照文章徒手教你建自己的博客,文章里有搭建免费博客的详细步骤。
Jacklin999
2018/09/12
9730
为Next主题添加多说评论系统
Gitalk-基于Github项目issue的评论系统在博客系统中实践
描述: 我想对于所有使用hexo、Hugo或者WordPress自建博客的博主来说GitTalk应该不陌生,GitTalk通过Github的OpenAPI以及issues功能实现社区评论确实还是很方便的,除开对国内访问速度较慢就没啥毛病,但是考虑到新手朋友此处还是简单介绍一下。
全栈工程师修炼指南
2022/09/29
1.9K0
Gitalk-基于Github项目issue的评论系统在博客系统中实践
相关推荐
Hexo博客集成码云评论系统
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档