【三维几何学习】从零开始网格上的深度学习-2:卷积网络CNN篇(Pytorch)

本文参加新星计划人工智能(Pytorch)赛道:https://bbs.csdn.net/topics/613989052

从零开始网格上的深度学习-2:卷积网络CNN篇

  • 引言
  • 一、概述
    • 1.1 卷积操作简述
    • 1.2 网格上的面卷积
  • 二、核心代码
    • 2.1 面卷积
    • 2.2 网络框架
  • 三、基于CNN的网格分类
    • 3.1 分类结果
    • 3.2 全部代码

引言

本文主要内容如下:

  • 介绍网格上基于面元素的卷积操作
  • 参考最新的CNN网络模块-ConvNeXt1:A ConvNet for the 2020s,构造网格分类网络

一、概述

1.1 卷积操作简述

卷积网络的核心:卷积操作就是数据元素特征与周围元素特征加权求和的一个计算过程。由卷积层实现,包括步长、卷积核大小等参数。

详情可百度或参考2:python 关于CNN的一些思考-2022

1.2 网格上的面卷积

在这里插入图片描述

无论水密or非水密的网格,其上的面并不是规则排列的。但对于三角形网格来说,每个面周围存在三个面,借助以上特性可对每个面构造1 × \times × 4的卷积区域,然后借助Pytorch即可轻松将CNN应用到网格的面上,称其为面卷积

二、核心代码

2.1 面卷积

主要参考MeshCNN3:A Network with an Edge中边卷积的代码即可,自己造的轮子没人家的好用…

  • 让网格面及其邻面形成 ∣ F ∣ × |F| \times F× 4的矩阵结构, ∣ F ∣ |F| F是面的个数。类似2D图像的长 × \times ×
  • 调用Pytorch中的Conv2d 或 Conv1d即可
class FaceConv(nn.Module):
    """
        Face convolution with convolution region (参考 MeshCNN)
    """

    def __init__(self, dim_in, dim_out, k, groups=1, bias=True):
        super(FaceConv, self).__init__()
        self.conv = nn.Conv2d(dim_in, dim_out, kernel_size=(1, k), groups=groups, bias=bias)
        self.k = k

    def __call__(self, edge_f, mesh):
        return self.forward(edge_f, mesh)

    def forward(self, x, mesh):
        if self.k == 1:
            # x = x.squeeze(-1)
            x = self.conv(x)
        else:
            x = x.squeeze(-1)
            G = torch.cat([self.pad_gemm(i, x.shape[2], x.device) for i in mesh], 0)  # batchsize
            G = self.create_GeMM(x, G)
            x = self.conv(G)
        return x

    def flatten_gemm_inds(self, Gi):
        (b, ne, nn) = Gi.shape
        ne += 1
        batch_n = torch.floor(torch.arange(b * ne, device=Gi.device).float() / ne).view(b, ne)
        add_fac = batch_n * ne
        add_fac = add_fac.view(b, ne, 1)
        add_fac = add_fac.repeat(1, 1, nn)
        Gi = Gi.float() + add_fac[:, 1:, :]
        return Gi

    def create_GeMM(self, x, Gi):
        Gishape = Gi.shape
        padding = torch.zeros((x.shape[0], x.shape[1], 1), requires_grad=True, device=x.device)
        x = torch.cat((padding, x), dim=2)
        Gi = Gi + 1
        Gi_flat = self.flatten_gemm_inds(Gi)
        Gi_flat = Gi_flat.view(-1).long()
        odim = x.shape
        x = x.permute(0, 2, 1).contiguous()
        x = x.view(odim[0] * odim[2], odim[1])
        f = torch.index_select(x, dim=0, index=Gi_flat)
        f = f.view(Gishape[0], Gishape[1], Gishape[2], -1)
        f = f.permute(0, 3, 1, 2).contiguous()   # 不加contiguous有时候会报错 - 中断训练

        return f

    def pad_gemm(self, m, xsz, device):
        padded_gemm = torch.tensor(m.mesh_nb[:, 0:self.k - 1], device=device).float().requires_grad_()
        padded_gemm = torch.cat((torch.arange(len(m.faces), device=device).float().unsqueeze(1), padded_gemm), dim=1)
        padded_gemm = F.pad(padded_gemm, (0, 0, 0, xsz - len(m.faces)), "constant", 0)
        padded_gemm = padded_gemm.unsqueeze(0)
        return padded_gemm

2.2 网络框架

网络框架主要参考ConvNeXt1:A ConvNet for the 2020s
在这里插入图片描述

  • 主要借用了其中的ConvNeXt Block,通道可分离卷积 - LN - conv1x1up - GELU - conv1x1dn
  • 由于本文使用数据集较小,主要由几个Block串联组成分类网络
class TriCNN(nn.Module):
    def __init__(self, dim_in, dims, classes_n=30):
        super(TriCNN, self).__init__()
        self.dims = dims[0:]
        self.first_conv = FaceConv(dim_in, dims[0], k=4, groups=1, bias=True)
        self.first_ln = nn.LayerNorm(dims[0], eps=1e-6)
        self.conv1x1up = nn.Conv1d(dims[0], dims[0] * 2, 1)
        self.act = nn.GELU()
        self.conv1x1dn = nn.Conv1d(dims[0] * 2, dims[0], 1)
        for i, dim in enumerate(self.dims[:]):
            setattr(self, 'Block{}'.format(i), Block(dim, k=4))
        self.last_ln = nn.LayerNorm(dims[-1], eps=1e-6)

        # cls
        self.gp = nn.AdaptiveAvgPool1d(1)
        self.dp1 = nn.Dropout(0.5)
        self.fc = nn.Linear(self.dims[-1], classes_n)

    def forward(self, x, mesh):
        x = x.permute(0, 2, 1).contiguous()
        # 1.first Block
        x = self.first_conv(x, mesh)
        x = x.squeeze(-1)
        x = x.permute(0, 2, 1).contiguous()
        x = self.first_ln(x)
        x = x.permute(0, 2, 1).contiguous()
        x = self.conv1x1up(x)
        x = self.act(x)
        x = self.conv1x1dn(x)

        # 2.Blocks
        for i in range(len(self.dims) - 1):
            x = getattr(self, 'Block{}'.format(i))(x, mesh)

        # 3.final
        x = x.squeeze(-1)
        x = x.permute(0, 2, 1).contiguous()
        x = self.last_ln(x)
        x = x.permute(0, 2, 1).contiguous()

        # 4.cls
        x = self.gp(x)
        x = x.view(-1, self.dims[-1])
        x = self.dp1(x)
        x = self.fc(x)
        return x


class Block(nn.Module):
    def __init__(self, dim, k=10, bias=True):
        super(Block, self).__init__()
        self.conv = FaceConv(dim, dim, groups=dim, k=k, bias=bias)
        self.ln = nn.LayerNorm(dim,eps=1e-6)
        self.conv1x1up = nn.Conv1d(dim, dim * 2, 1)
        self.act = nn.GELU()
        self.conv1x1dn = nn.Conv1d(dim * 2, dim, 1)
        self.w = nn.Parameter(torch.zeros(1))

    def forward(self, x, mesh):
        identity = x
        x = self.conv(x, mesh)
        x = x.squeeze(-1)
        x = x.permute(0, 2, 1).contiguous()
        x = self.ln(x)
        x = x.permute(0, 2, 1).contiguous()
        x = self.conv1x1up(x)
        x = self.act(x)
        x = self.conv1x1dn(x)
        x = x * self.w
        x = x + identity
        return x

三、基于CNN的网格分类

数据集是SHREC’11 可参考三角网格(Triangular Mesh)分类数据集 或 MeshCNN

3.1 分类结果

在这里插入图片描述在这里插入图片描述
学习率还是有点高,权重波动很大,期间最高准确率是99.67
在这里插入图片描述

不得不提一句,其在网格分割上的准确率不如UNet形式的网络,也可能是没加入池化的锅…

3.2 全部代码

DataLoader代码请参考4:从零开始网格上的深度学习-1:输入篇(Pytorch)

import torch
import torch.nn as nn
import torch.nn.functional as F
from DataLoader_shrec11 import DataLoader
from DataLoader_shrec11 import Mesh


class TriCNN(nn.Module):
    def __init__(self, dim_in, dims, classes_n=30):
        super(TriCNN, self).__init__()
        self.dims = dims[0:]
        self.first_conv = FaceConv(dim_in, dims[0], k=4, groups=1, bias=True)
        self.first_ln = nn.LayerNorm(dims[0], eps=1e-6)
        self.conv1x1up = nn.Conv1d(dims[0], dims[0] * 2, 1)
        self.act = nn.GELU()
        self.conv1x1dn = nn.Conv1d(dims[0] * 2, dims[0], 1)
        for i, dim in enumerate(self.dims[:]):
            setattr(self, 'Block{}'.format(i), Block(dim, k=4))
        self.last_ln = nn.LayerNorm(dims[-1], eps=1e-6)

        # cls
        self.gp = nn.AdaptiveAvgPool1d(1)
        self.dp1 = nn.Dropout(0.5)
        self.fc = nn.Linear(self.dims[-1], classes_n)

    def forward(self, x, mesh):
        x = x.permute(0, 2, 1).contiguous()
        # 1.first Block
        x = self.first_conv(x, mesh)
        x = x.squeeze(-1)
        x = x.permute(0, 2, 1).contiguous()
        x = self.first_ln(x)
        x = x.permute(0, 2, 1).contiguous()
        x = self.conv1x1up(x)
        x = self.act(x)
        x = self.conv1x1dn(x)

        # 2.Blocks
        for i in range(len(self.dims) - 1):
            x = getattr(self, 'Block{}'.format(i))(x, mesh)

        # 3.final
        x = x.squeeze(-1)
        x = x.permute(0, 2, 1).contiguous()
        x = self.last_ln(x)
        x = x.permute(0, 2, 1).contiguous()

        # 4.cls
        x = self.gp(x)
        x = x.view(-1, self.dims[-1])
        x = self.dp1(x)
        x = self.fc(x)
        return x


class Block(nn.Module):
    def __init__(self, dim, k=10, bias=True):
        super(Block, self).__init__()
        self.conv = FaceConv(dim, dim, groups=dim, k=k, bias=bias)
        self.ln = nn.LayerNorm(dim,eps=1e-6)
        self.conv1x1up = nn.Conv1d(dim, dim * 2, 1)
        self.act = nn.GELU()
        self.conv1x1dn = nn.Conv1d(dim * 2, dim, 1)
        self.w = nn.Parameter(torch.zeros(1))

    def forward(self, x, mesh):
        identity = x
        x = self.conv(x, mesh)
        x = x.squeeze(-1)
        x = x.permute(0, 2, 1).contiguous()
        x = self.ln(x)
        x = x.permute(0, 2, 1).contiguous()
        x = self.conv1x1up(x)
        x = self.act(x)
        x = self.conv1x1dn(x)
        x = x * self.w
        x = x + identity
        return x


class FaceConv(nn.Module):
    """
        Face convolution with convolution region (参考 MeshCNN)
    """

    def __init__(self, dim_in, dim_out, k, groups=1, bias=True):
        super(FaceConv, self).__init__()
        self.conv = nn.Conv2d(dim_in, dim_out, kernel_size=(1, k), groups=groups, bias=bias)
        self.k = k

    def __call__(self, edge_f, mesh):
        return self.forward(edge_f, mesh)

    def forward(self, x, mesh):
        if self.k == 1:
            # x = x.squeeze(-1)
            x = self.conv(x)
        else:
            x = x.squeeze(-1)
            G = torch.cat([self.pad_gemm(i, x.shape[2], x.device) for i in mesh], 0)  # batchsize
            G = self.create_GeMM(x, G)
            x = self.conv(G)
        return x

    def flatten_gemm_inds(self, Gi):
        (b, ne, nn) = Gi.shape
        ne += 1
        batch_n = torch.floor(torch.arange(b * ne, device=Gi.device).float() / ne).view(b, ne)
        add_fac = batch_n * ne
        add_fac = add_fac.view(b, ne, 1)
        add_fac = add_fac.repeat(1, 1, nn)
        Gi = Gi.float() + add_fac[:, 1:, :]
        return Gi

    def create_GeMM(self, x, Gi):
        Gishape = Gi.shape
        padding = torch.zeros((x.shape[0], x.shape[1], 1), requires_grad=True, device=x.device)
        x = torch.cat((padding, x), dim=2)
        Gi = Gi + 1
        Gi_flat = self.flatten_gemm_inds(Gi)
        Gi_flat = Gi_flat.view(-1).long()
        odim = x.shape
        x = x.permute(0, 2, 1).contiguous()
        x = x.view(odim[0] * odim[2], odim[1])
        f = torch.index_select(x, dim=0, index=Gi_flat)
        f = f.view(Gishape[0], Gishape[1], Gishape[2], -1)
        f = f.permute(0, 3, 1, 2).contiguous()   # 不加contiguous有时候会报错 - 中断训练

        return f

    def pad_gemm(self, m, xsz, device):
        padded_gemm = torch.tensor(m.mesh_nb[:, 0:self.k - 1], device=device).float().requires_grad_()
        padded_gemm = torch.cat((torch.arange(len(m.faces), device=device).float().unsqueeze(1), padded_gemm), dim=1)
        padded_gemm = F.pad(padded_gemm, (0, 0, 0, xsz - len(m.faces)), "constant", 0)
        padded_gemm = padded_gemm.unsqueeze(0)
        return padded_gemm


if __name__ == '__main__':
    # 输入
    data_train = DataLoader(phase='train')         # 训练集
    data_test = DataLoader(phase='test')           # 测试集
    dataset_size = len(data_train)                 # 数据长度
    print('#training meshes = %d' % dataset_size)  # 输出模型个数

    # 网络
    net = TriCNN(data_train.input_n, [128, 128], data_train.class_n)    # 创建网络 以及 优化器
    optimizer = torch.optim.Adam(net.parameters(), lr=0.001, betas=(0.9, 0.999))
    net = net.cuda(0)
    loss_fun = torch.nn.CrossEntropyLoss(ignore_index=-1)
    num_params = 0
    for param in net.parameters():
        num_params += param.numel()
    print('[Net] Total number of parameters : %.3f M' % (num_params / 1e6))
    print('-----------------------------------------------')

    # 迭代训练
    for epoch in range(1, 201):
        print('---------------- Epoch: %d -------------' % epoch)
        for i, data in enumerate(data_train):
            # 前向传播
            net.train(True)        # 训练模式
            optimizer.zero_grad()  # 梯度清零
            face_features = torch.from_numpy(data['face_features']).float()
            face_features = face_features.to(data_train.device).requires_grad_(True)
            labels = torch.from_numpy(data['label']).long().to(data_train.device)
            out = net(face_features, data['mesh'])     # 输入到网络

            # 反向传播
            loss = loss_fun(out, labels)
            loss.backward()
            optimizer.step()              # 参数更新

        # 测试
        net.eval()
        acc = 0
        for i, data in enumerate(data_test):
            with torch.no_grad():
                # 前向传播
                face_features = torch.from_numpy(data['face_features']).float()
                face_features = face_features.to(data_test.device).requires_grad_(False)
                labels = torch.from_numpy(data['label']).long().to(data_test.device)
                out = net(face_features, data['mesh'])
                # 计算准确率
                pred_class = out.data.max(1)[1]
                correct = pred_class.eq(labels).sum().float()
            acc += correct
        acc = acc / len(data_test)
        print('epoch: %d, TEST ACC: %0.2f' % (epoch, acc * 100))

  1. A ConvNet for the 2020s ↩︎ ↩︎

  2. python 关于CNN的一些思考-2022 ↩︎

  3. MeshCNN: A Network with an Edge ↩︎

  4. 从零开始网格上的深度学习-1:输入篇(Pytorch) ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/547.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FPGA之时钟规划图解

目录 一、前言 二、时钟规划概念 三、时钟规划的模块 四、时钟规划之时钟单元布局 4.1 BUFG 4.2 BUFH 4.3 BUFR 4.4 BUFIO 五、时钟规划之时钟单元走线 5.1 BUFG->BUFH 5.2 BUFR->FF 5.3 BUFIO->FF 一、前言 对于vivado这类使用verilog语言的进…

《Netty》从零开始学netty源码(七)之NioEventLoop.selectStrategy

NioEventLoop是一个事件轮询器,在它的run方法中其实是一个for死循环,不断重复三个过程:1. 获取IO事件,2. 处理IO事件,3. 处理任务队列中的task,而SelectStractegy就是用于第一步获取IO事件,它的…

css:使用filter和backdrop-filter实现高斯模糊效果

背景 今天接到一个需求是,使用高斯模糊的效果对一个页面进行模糊处理,正好借这个机会来整理一下 css3 中高斯模糊的两个 API API介绍 filter 说明: 该 API 是一个过滤器,不仅能实现高斯模糊,还有很多比如颜色偏移、…

接口文档包含哪些内容?怎么才能写好接口文档?十年测试老司机来告诉你

目录 接口文档结构 参数说明 示例 错误码说明 语言基调通俗易懂 及时更新与维护 总结 那么我们该如何写好一份优秀的接口文档呢? 接口文档结构 首先我们要知道文档结构是什么样子的。接口文档应该有清晰明确的结构,以便开发人员能快速定位自己需…

经典文献阅读之--Dynamic-VINS(动态点滤除VINS)

0. 简介 现在的SLAM算法在静态环境中表现良好,但在动态环境中很容易失败。最近的工作将基于深度学习的语义信息引入到SLAM系统以减轻动态对象的影响。然而,在资源受限的机器人的动态环境中应用鲁棒定位仍然具有挑战性。所以《RGB-D Inertial Odometry f…

ES+Redis+MySQL,这个高可用架构设计太顶了!

一、背景 会员系统是一种基础系统,跟公司所有业务线的下单主流程密切相关。如果会员系统出故障,会导致用户无法下单,影响范围是全公司所有业务线。所以,会员系统必须保证高性能、高可用,提供稳定、高效的基础服务。 …

vue笔记

第一个Vue应用 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"widthdevice-…

【零基础入门前端系列】—动画和弹性盒模型(二十四)

【零基础入门前端系列】—动画和弹性盒模型&#xff08;二十四&#xff09; 一、概念 动画是使元素从一种样式逐渐变化为另一种样式&#xff0c;你可以改变任意多的样式任意多的次数。 请用百分比来规定变化发生的时间&#xff0c;或用关键词from和to&#xff0c;等同0%和10…

购物清单(蓝桥杯C/C++省赛)

目录 1 问题描述 2 文件的读取格式 3 代码实现 1 问题描述 小明刚刚找到工作&#xff0c;老板人很好&#xff0c;只是老板夫人很爱购物。老板忙的时候经常让小明帮忙到商场代为购物。小明很厌烦&#xff0c;但又不好推辞。 这不&#xff0c;XX大促销又来了&#xff01;老板…

项目实战典型案例26——nacos的命名空间名称和id不一致带来的思考

nacos的命名空间名称和id不一致带来的思考一&#xff1a;背景介绍Nacos命名空间相关知识点思考总结一&#xff1a;背景介绍 项目用的naocs做的配置中心和服务发现。由于开发环境和本地环境使用的都是同一个命名空间&#xff0c;我们多个服务相互调用的时候&#xff0c;由于开发…

若依分离版下拉框动态加载

最近在学习使用若依分离版框架&#xff0c;想要实现下拉框动态加载另一张表的数据&#xff0c;于是参考【字典数据-字典名称】的实现方式&#xff0c;成功试下下拉框动态加载&#xff0c;做下记录 涉及表格&#xff1a;his_user&#xff08;用户表&#xff09;-- 用户管理&…

【linux】:进程概念

文章目录 冯诺依曼体系结构一&#xff1a;操作系统二: 进程总结冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 冯诺依曼体系如下图&#xff1a; 那么输入设备有哪些呢&#xff1f…

常见的Web安全漏洞:SYN攻击/CSRF/XSS

一、SYN攻击&#xff08;属于DOS攻击&#xff09; 什么情况下被动方出现SYN_RCVD状态?(flood攻击服务) 客户伪造 ip 端口&#xff0c; 向服务端发送SYN请求。完成2次握手&#xff0c;第三次服务端 等待客户端ACK确认&#xff0c;但由于客户不存在服务端一直未收到确认&#…

内含18禁~~关于自学\跳槽\转行做网络安全行业的一些建议

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。所以可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。…

金三银四,我猜你需要这套网络安全工程师面试题合集

2023年已经开始了&#xff0c;先来灵魂三连问&#xff0c;年初定的目标是多少&#xff1f;薪资能涨吗&#xff1f;女朋友能找到吗&#xff1f; 好了&#xff0c;不扎大家的心了&#xff0c;接下来进入正文。 由于我之前写了不少网络安全技术相关的文章和回答&#xff0c;不少…

过来人告诉你:Java学到什么程度可以找工作?

大部分初次学习Java的同学都非常关注自己学到什么程度可以找工作就业&#xff0c;因为学习的目的一方面在于掌握知识、提高技能&#xff0c;另一方面就是就业谋生。今天笔者就来跟大家聊一聊一下Java学习到什么地步可以面试找工作。任何企业&#xff0c;不论大小&#xff0c;对…

exe反编译为.py文件

介绍公司以前的一个exe包&#xff0c;我们需要查看里面python源码&#xff0c;但是以前的py源码文件找不到&#xff0c;所以只能反编译&#xff0c;介绍一下反编译的过程。首先准备&#xff1a;pyinstxtractor.py这个文件&#xff0c;网上很多&#xff0c;自己下载准备查看二进…

十八、动画与canvas

1.RequestAnimationFrame 早期定时动画 setTimeout和setInterval不能保证时间精度&#xff0c;第二个参数只能保证何时将代码添加到浏览器的任务队列 requestAnimationFrame(cb)的cb在浏览器重绘屏幕前调用 function updateProgress(){const div document.getElementById(d…

昨天某读者拿到华为OD岗位offer,今天来分享一下经验,包含华为OD机试

来自读者投稿&#xff0c;已经拿到华为 OD 开发岗位 offer&#xff0c;询问了一些问题&#xff0c;下面是他的一些经验。 文章目录华为 OD 投递简历华为 OD 机试分数OD 机试通过之后&#xff0c;收到综合测评OD 技术面&#xff08;时长 1 小时左右&#xff09;主管/HR 面试&…

从参数数量视角理解深度学习神经网络算法 DNN, CNN, RNN, LSTM 以python为工具

从参数数量视角理解深度学习神经网络算法 DNN, CNN, RNN, LSTM 以python为工具 文章目录1. 神经网络数据预处理1.1 常规预测情景1.2 文本预测场景2.全连接神经网络 DNN3.卷积神经网络CNN4.循环神经网络 RNN5.长短期记忆神经网络 LSTMʚʕ̯•͡˔•̯᷅ʔɞʚʕ̯•͡˔•̯᷅ʔ…