谈谈DeepSDF中AutoDecoder
epi.clyce2020/01/15原创 软件综合 IP:香港

内容同步发布于简书以及知乎




前阵子读了一篇关于三维重建的论文,是Facebook团队联合华盛顿大学发表在CVPR2019上的。这一论文尝试用神经网络去近似SDF函数(将在后面进行简述),进而重建三维模型。

在阅读过程中我对其中的AutoDecoder概念产生了一定兴趣,特此将一些随想记录下来。

在谈论AutoDecoder之前,我想先用最少的文字说说这个传说中のDeepSDF

用最简单易懂的语言来说,对于一个已知的三维模型,SDF(Signed Distance Function)就是一个三维空间中的函数,输入一个坐标点  1579077764344.svg  ,返回该点到三位模型表面的距离。同时这个距离是有符号的,一般来说,对于模型外面的点,距离为正,而模型内部的点则为负。或者从观察者的角度,如果点在表面面向观察者的一侧,则为正,在背离观察者的一侧,则为负。

引用DeepSDF论文官方的兔兔说明一下:

1579077766300.jpg


各个方法对于SDF函数的存储往往是离散的,比如将三维空间是做一系列的“体素”(类比二维空间的“像素”),每个体素中存储一个SDF值,之后尝试用体素中等于或接近零的部分重建一个三维表面。

而DeepSDF论文呢,则尝试将SDF解析为一个连续的函数。虽然文章把这一点作为一个创新提出(包括将模型表面视作SDF回归的决策边界),其实我觉得这个很早应该就有人想到了,因为根本上来讲,SDF本来就应该是一个连续函数才对,只是受制于各种原因,我们无法为每个三维物体写出这个连续函数的完整形式。这也就是DeepSDF的思路,用神经网络这个“万能插值机”去近似这一函数。

事实上我之前也想过这个方向,也相信许多人尝试过这个思路。不过就像大多数DeepLearning落地的应用一样,真正需要做的工作在于如何科学地编码输入输出,以及如何Encapsulate你自己的领域知识到神经网络的结构和训练过程当中去

文章一开始提出了一个显而易见的结构,就是直接拿个神经网络,输入坐标,输出SDF值,然后对每一个三维模型单独训练,充分发挥“插值机”的原始作用。不过显然,这种方法在现实中是很难应用的,因为对于每个新模型都要训练一个新神经网络,实在是太低效太不~~Generalizablism~~了,那么自然地,为了发挥深井网络Generalizability的优势,团队自然而然想到了使用一个Latent Vector去表示三维模型的原始形态,于是新的神经网络就变成了,给定一个用来表示三维模型的Latent Vector,附带一个用以查询的坐标点,返回这个坐标点所在位置的SDF值。

具体的神经网络结构和LossFunction的设计就不详述了,非常Straight forward,扫一眼论文就明白。

众所周知(~~并不~~)获得一个原始输入数据的Latent Vector Representation,大家最喜欢的方法之一就是训练一个AutoEncoder。然而DeepSDF则使用了一个稍有不同的东西,他们叫做AutoDecoder,故名思义,就是一个不训练Encoder的Decoder。

这里我想吐个大嘈,他们的论文说了一大堆不要Encoder的理由,比如AutoEncoder大家一般都只用一半而把另一半另一半扔掉什么的,说来说去没说到点上。强烈怀疑他们组提出使用AutoDecoder架构的人和最后执笔写论文的人沟通不畅。我自己读下来的感觉是,AutoDecoder的选用本质上是一个在FeedForward过程的速度,和数据本身潜在的复杂度和变数之间的取舍(如果我的理解有错,欢迎指正)

下面简单讲一下这个传说中の AutoDecoder:


1579077766293.jpg


如图所示,标准AutoEncoder的架构大体上是,一个输入,若干层(称为Encoder)之后有一个关键的瓶颈层(Code),瓶颈层后面若干层(称为Decoder)之后有一个和输入层长得一样的输出层。训练的时候努力让输出等于(甚至优于——比如修复类任务)输入,最后训练出来的瓶颈层,就是对输入数据的一种更紧凑的压缩,也叫做关于原始数据的Latent Vector。Latent Vector往往包含了原始数据最关键的那些信息,更加便于提取特征,等等等等。

而DeepSDF则提出,我们可以不要Encoder部分,而直接取用Decoder部分

这就很TM诡异了,如果我只有一个Decoder,我怎么知道哪个Latent Vector对应原始输入呢?

官方给出的示意图长这样:

1579077766299.jpg


Hmm...不是很好懂,没错,不仅这图不太好懂,论文里的描述也乱七八糟,不过且慢,容我再画一张图:


1579077767405.jpg


这张图里,我们加入了一个额外的输入  1579077769487.svg  ,称为第0层,而 Code 层则是一个纯 Linear 层,那么第 0 层对 Code 层的权重  1579077769498.svg  即是我们要的 Latent Vector。

另外,Decoder 不必是解码成原始数据的样子,而是只要解码成目标函数的输出(比如SDF值)就好了。

那么对于DeepSDF来说,其结构事实上可以理解成:

1. 第 0 层是一个恒为1的输入,

2. Code 层和要查询的  1579077770943.svg  一起输入到后面的深度神经网络

3. 最后输出SDF函数

而这个训练过程则是:

  1. 对每个模型 1579077772837.svg ,初始化一个独立的Latent Vector 1579077773009.svg  (嗯,原论文用  1579077773010.svg  表示Latent Vector,不过为了避免和空间坐标点  1579077774793.svg  产生迷惑,我在这里用  1579077775908.svg  代替了)

  2. 然后对于每个  1579077776593.svg  样本,选择该Vector作为到Code层的权重,即  1579077778181.svg  ,

  3. 再对整个神经网络(包含  1579077778182.svg  )进行训练。

这样子训练下来,每个训练样本都渐渐就训练出了自己的Latent Vector,而Decoder则学习到了对每个Latent Vector,如何匹配其要查询的  1579077779243.svg  对应的sdf值。

可是既然这样,每个训练数据训练出了自己独立的Latent Vector,那么面对新的数据(比如测试数据集)该怎么办?论文里轻描淡写了一个  1579077780267.svg  ,可是这个  1579077782041.svg  是怎么做的呢?难道还是对每个新数据重新训练一番?!

这里不得不吐槽一下,这个项目不仅论文抓不住重点,代码也写得乱七八糟,读得人晕头转向,重建的核心部分居然还存在# TODO: why is this needed这样的注释。我准备过一阵子专门针对机器学习界软件工程基础薄弱,代码凌乱不堪的现象开一篇文章。

于是带着迷惑我又去看了一遍这个项目的源码,然后发现还真的(差不多)是这样 。。。

不过比起直接用  1579077783251.svg  作为输入的那个版本,这个版本在面对新数据  1579077785187.svg  的时候,锁定了整个Decoder,也就是说保持  1579077784706.svg  的映射不变,而只训练新的  1579077785185.svg  ,大大减少了需要训练的参数数量。并且因为Decoder经过之前的训练,已经包含了关于训练数据的先验知识,所以这次Latent Vector训练的收敛将会十分迅速。

其实这里面我有一个小小的疑惑,为什么不保留论文最开始的结构(只输入  1579077787272.svg  ),但是对于每个不同的模型,独立训练输入层对第一个隐藏层的权重,这样输入层对第一个隐藏层的变换可以视作一个“Latent Function”(对应“Latent Vector”的概念),将  1579077788464.svg  的值变换为该模型对应的,隐空间下该点的特征。这样一来,神经网络的参数数量能够减少,并且也能够更好地表达Latent Coding和空间坐标之间的耦合关系。这一方面的比较和分析,如果有时间,我也有可能会去做一做。

所以,答案是,AutoDecoder的结构不能像AutoEncoder一样面对新数据时只需要一个Forward过程,而是需要根据后面的Decoder中隐含的先验知识,进行少量的训练,以找到一个Latent Code出来。

那么问题来了,为什么要使用一个无法进行实时推断而是每次都要训练的AutoDecoder来代替原有的AutoEncoder呢?

一个不那么具备说服力(但是论文里似乎确实提出了)的理由是,因为Decoder不必还原原始数据,而可以是近似目标函数,所以训练出来的Latent Vector,对于我们目标函数所需要的信息会更加友好。但是仔细想一想就会发现,如果我们直接使用原始数据进行输入,构建一个神经网络,然后在某一隐藏层拼接上  1579077789329.svg  查询。那么训练之后,我们就可以得到一个在预测时只需要Forward过程而无需再训练的神经网络,而被拼接了  1579077790563.svg  的那一层也就可以视作Latent Vector。

所以……用AutoDecoder的深层原因到底是什么?

于是我去费劲巴拉地又读了一遍他们的代码,着重看了看Reconstruction部分,想办法搞明白这个神经网络到底在做什么之后,谜题便渐渐解开了。

首先,这个模型在重建的时候到底在干什么?这个问题在我读论文和代码的时候一度困扰了我很久,尤其是在我看到这个预期输出是SDF Value的神经网络在 Test - Evaluation 阶段,依然在测试数据集中包含SDF Value进行训练。进一步捋顺之后我反应过来他们在神经网络中的那一步Evaluation,实际上是在衡量这个神经网络能构造出一个多好的Latent Vector来近似测试数据集中的SDF Value,而不是尝试计算Reconstruct Error。而在真正的表面重建过程中,这个收敛出来的Latent Vector将和任意输入坐标一起得出一个SDF Value,进而实现任意精度的重建。

那么问题就逐渐明朗了,DeepSDF在做的事情,其实是对于有限精度采样的SDF数据,在包含先验知识的Decoder的帮助下,回归出一个连续的SDF函数,从而实现将有穷精度的数据转换成任意精度的查询器,以便进行精细重建。

返回来看这个AutoDecoder在测试数据上的训练过程(即寻找Latent Vector的过程),我们发现,可以将一个模型的所有采样点作为一个完整的epoch,而不是把单个模型的全部采样数据一股脑输入进去。这么做的好处是什么呢?

  1. 如果是AutoEncoder,为了得到代表一整个模型的Latent Vector,我们将不得不想办法统一模型原始数据的分辨率(比如每三个神经元代表一个采样点,那么一个模型最多只能有input_size / 3个采样点)。而对于AutoDecoder,一个模型对应的采样数量只影响其epoch的大小,而不会影响单个输入本身的尺寸,所以AutoDecoder的架构支持对每个输入模型有任意数量的采样点。

  2. 另一个非常重要的是,对于模型的采样点,一个显著的特征就是它是Orderless的,即你交换任意两个采样点的顺序,也不应当影响最终输出的结果。传统的神经网络结构并不是很善于解码orderless vector的输入,但是如果我们不将整个模型的所有采样点编码为一个巨大的input vector,而是将每个采样点作为一个input vector,整个模型的所有采样点作为一个epoch去训练,那么我们就可以应对这种orderless的特征了。

综上,我们可以得到AutoDecoder结构与AutoEncoder以及论文最开始提出的  1579077791408.svg  结构(下面简称xyz结构)进行比较:

  1. AutoDecoder vs xyz结构:

  • - 对于新的模型,可以比xyz更快速地收敛成一个可靠的查询器

  • - 经过训练的Decoder包含了来自训练数据集的先验知识,使得对于SDF的回归并不是单纯地“插值”操作,而是考虑了模型本身附带的意义而进行的合理补全。

  • 2. AutoDecoder vs AutoEncoder

  • - AutoDecoder能够处理任何尺寸的采样数据,而不必像AutoEncoder那样将一个模型的全部采样点包含在一个定长的input vector中

  • - AutoDecoder将单独的Input Vector转换成Epoch的操作,使得采样的顺序能够被很好地解耦,适合处理orderless的采样数据

  • - 相比AutoEncoder来说,AutoDecoder面对每一个新数据时都需要一个训练过程来找到合适的Latent Vector,无法在需要相对实时的领域应用

到这里,AutoDecoder的具体合适的应用场景,可谓呼之即出了。

不过最后我想说一个自己的疑惑,不知道大家怎么看待:

将单个模型的所有采样数据作为一个Epoch而非一整个Input Vector去获取Latent Vector的这一操作,是否因为神经网络的Independence Assumption而丢失了邻接节点的互相影响呢?

鄙人不才,没想到足够的支持或者否定,还希望大家讨论点拨


[修改于 4年11个月前 - 2020/01/15 16:56:01]

来自:计算机科学 / 软件综合
1
 
2
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
epi.clyce
学者 机友 笔友
文章
345
回复
2156
学术分
21
2007/07/10注册,3个月19天前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}