激光雷达-Resnet-推荐系统分析

激光雷达-Resnet-推荐系统分析

参考文献链接

https://mp.weixin.qq.com/s/LoCJnKa8Ty2ytCbXS2jPow
https://www.zhihu.com/question/64494691/answer/786270699?utm_campaign=shareopn&utm_medium=social&utm_oi=860016607857098752&utm_psn=1573769729172267008&utm_source=wechat_timeline
https://mp.weixin.qq.com/s/R-VmZQ4EJVf6gzyPJ5FKRQ
国外激光雷达公司被中国公司卷死:纷纷破产、退市、合并自救
美国当地时间11月8日,激光雷达上市公司Quanergy宣布当日收盘后暂停交易,纽约证券交易所决定根据《纽约交易所上市公司手册》第802.01B条对该公司启动退市程序,这是近期国外激光雷达公司的第三记爆锤。在2022年10月,号称全球车规级激光雷达鼻祖、24年前创立的Ibeo在官网上公告,德国汉堡法院已经批准其启动破产程序,这是国外激光雷达领域的第一记爆锤。在Quanergy宣布退市的前不到48小时,激光雷达鼻祖的Velodyne和Ouster两家上市公司宣布合并,合并后的市值仅为4亿美元,现金储备仅为3.55亿美元,这是国外激光雷达领域的第二记爆锤。在Velodyne和Ouster合并当天,中国激光雷达公司禾赛科技CEO李一帆发了一个朋友圈,意思是禾赛收入比美股8家上市公司(激光雷达领域,编者注)的总和……(意思可能是还要多),现在只剩下7家了。李一帆可能也没想到,48小时后只剩下6家了。在国外激光雷达公司纷纷宣布破产、退市、合并自救的同时期,禾赛科技在11月2日发布了面向ADAS前装量产车的纯固态近距补盲激光雷达FT120,紧接着速腾聚创在11月7日发布了全固态补盲激光雷达RS-LiDAR-E1。这两家中国头部激光雷达公司几乎同时发布固态补盲激光雷达,也预示了激光雷达行业发展趋势,接下来就看紧抱蔚来大腿的图达通是否有类似的产品规划了。从激光雷达行业的发展来看,这个最先源自国外的激光雷达上车的思路,最终还是中国车企和激光雷达公司联手将其规模上车,国外车企对激光雷达上车的规划略微保守,也造成了现阶段国外激光雷达公司处境艰难,而中国激光雷达公司则因为遇到更激进、更内卷的新能源造车运动而迎来爆发之年。
01国外激光雷达公司上市易、产品上车难
随着Velodyne和Ouster的合并,以及Quanergy的退市,在美股上市的激光雷达公司少了两家,剩下的几家激光雷达上市公司,似乎也处境艰难,除了激光雷达第一股Luminar市值仍旧维系在十位数的市值达到26亿美元外,其他激光雷达上市公司市值剩下9位数了,Quanergy退市是因为市值不足1500万美元达到一定天数,退市前市值仅为1147万美元。
车智君对Luminar的印象,可能还停留在当年上汽R汽车宣布定点Luminar,但上汽的另一个新能源汽车品牌智己汽车定点的是速腾聚创,但Luminar在海外车企曾经定点的数量不少,但真正上车的不多,能够在中国市场量产上市搭载其产品的海外品牌车型一款都还没有,但中国搭载激光雷达的新上市车型全球最多,甚至有车企豪言——四颗以下不要说话。激光雷达上车陷入了硬件的无限内卷!至于宣布破产的Ibeo,车智君对其印象除了是全球首款搭载激光雷达量产车型奥迪A8的供应商,此外可能还停留在2020年12月,长城汽车宣布旗下WEY品牌车型将搭载3颗Ibeo全固态激光雷达,并且在2021年下半年量产。显然,无论是奥迪还是长城的量产计划并没有给Ibeo带来真正的量和规模化收益,否则Ibeo也不至于那么快宣布破产。

 

 自从Luminar抢先成为激光雷达第一股后,激光雷达公司纷纷把目光放到了IPO上,由于美股上市更快、甚至还有SPAC这个IPO加速包选项,Luminar上市后,陆陆续续有Velodyne、Ouster、Innoviz、Quanergy、Aeva、Cepton等激光雷达公司纷纷登陆美股。但这些激光雷达公司上市容易上车却很难,Innoviz曾在2022年5月宣布获得某车企高达40亿美元的激光雷达订单,但现如今,Innoviz市值仅为6.22亿美元,显然这个从2025年开始为期8年的计划尚未体现在Innoviz的市值上,二级市场投资人更担心的是,Innoviz的产品能不能上车。由于特斯拉铁了心要推纯视觉自动驾驶路线,海外车企品牌在激光雷达上车这个选择就很困难,考虑的因素包括了成本、产品成熟度、车规级、技术路线等等,福特和大众合资的Argo被关闭后,福特转向寻求量产自动驾驶而不是完全自动驾驶的解决方案,激光雷达被认为是完全自动驾驶的更优传感器则面临尴尬位置。

02中国激光雷达公司上市难、产品上车易
和海外激光雷达公司比,中国激光雷达行业呈现的则是完全相反的局面:那就是产品上车易、公司上市难。
在Luminar上市后带动一票激光雷达公司纷纷登陆二级市场,国内的激光雷达公司也有相关计划,最接近IPO的是禾赛科技,曾向科创板递交了招股书,但最终没有成功上市,后面转战海外IPO,有消息显示其IPO顺序就排在2021年6月30日IPO的滴滴后面,但滴滴直接引发了中概股监管风暴。另一个接近上市的激光雷达公司是速腾聚创,在完成了红筹架构搭建后,速腾聚创也被报道称秘密向港交所递交了上市申请,但迄今也没有走出这关键性一步,值得一提的是,速腾聚创随后更换了CFO。至于拿到蔚来全系车型定点的图达通,也有IPO的计划。但迄今为止,中国激光雷达领域尚未有一家公司成功登陆二级市场。现在不上可能也是对的,看看美股的激光雷达公司,市值变现最好的都是腰斩,最惨的都退市了。如果上市了,估值能否撑得住也是大考验,还不如现阶段趁着市场机会继续专注于扩大市场。中国激光雷达公司遇到了全球最卷的新能源汽车市场,造车新势力为了打造更好体验的智能驾驶,纷纷宣布新车型搭载激光雷达,甚至将其标配。小鹏P5应该是第一款量产交付搭载大疆Livox激光雷达的车型,蔚来则更激进,全系标配图达通的激光雷达,理想也不甘落后,L9标配禾赛的激光雷达,速腾则拿下最多的量产定点车型、更是拿下了中国新能源销量王者比亚迪的战略投资和合作。在2021年年底,沙龙汽车那句“四颗以下不要说话”,更是将中国激光雷达上车大战的内卷程度表现得淋漓尽致,这对激光雷达公司来说,是巨大的机会。2021年是激光雷达定点元年,2022年是激光雷达量产元年,但迄今为止,激光雷达在用户体验端的表现尚未展示出应有的水平。

 

 但新推出的新能源品牌及新车型,如果不搭载激光雷达,似乎都不要意思说自己是新能源汽车了。但激光雷达的量产使用也将会面临新的问题,出现交通事故、甚至是致命交通事故这样的灰犀牛事件,如何做好公众释疑可能就是一大挑战。短期来看,激光雷达上车这个趋势还在,但也需要推出搭载了激光雷达车型的车企,在智能驾驶领域的表现出加了更多传感器硬件带来的更优用户体验。另外,激光雷达公司还应该思考,一旦不及预期,甚至特斯拉纯视觉FSD完全拿掉司机,又应该如何应对。车智君的思考是,禾赛和速腾先后发布的补盲固态激光雷达,是否会有在性能上补充、在成本上替代部分摄像头的可能性呢?但需要补盲固态激光雷达的成本再降低,以接近甚至是低于高清摄像头的成本。至于高性能、高成本、高线速的主激光雷达,除了要向全(或纯)固态化发展外,还要等待或者是加速完全无人驾驶的到来,才能迎来更大的商业化机会。更激进的中国造车新势力,给了中国激光雷达公司机会,接下来就要看押注的依靠激光雷达实现完全自动驾驶这个科技树点对了,还是特斯拉完全自动驾驶纯视觉的科技树点对的。要知道,除了上述的禾赛、速腾、图达通,中国还有大量的激光雷达公司在前行。

Resnet到底在解决一个什么问题呢?
看了这个问题之后我思考了很久,于是写出了这篇专栏,现在贴过来当答案。
首先是跟着论文的思路走,了解作者提出resnet的“心路历程”,最后也有些个人整理的理解。
ps:欢迎关注我的专栏,这段时间我会持续更新,并且在更完约莫十几篇论文阅读后,会再写一些模型实现/代码方面的理解。
一、引言:为什么会有ResNet? Why ResNet?
神经网络叠的越深,则学习出的效果就一定会越好吗?
答案无疑是否定的,人们发现当模型层数增加到某种程度,模型的效果将会不升反降。也就是说,深度模型发生了退化(degradation)情况。
那么,为什么会出现这种情况?
1. 过拟合? Overfitting?
首先印入脑海的就是Andrew Ng机器学习公开课[1]的过拟合问题

 

 Andrew Ng的课件截图

在这个多项式回归问题中,左边的模型是欠拟合(under fit)的此时有很高的偏差(high bias),中间的拟合比较成功,而右边则是典型的过拟合(overfit),此时由于模型过于复杂,导致了高方差(high variance)。
然而,很明显当前CNN面临的效果退化不是因为过拟合,因为过拟合的现象是"高方差,低偏差",即测试误差大而训练误差小。但实际上,深层CNN的训练误差和测试误差都很大。

 

 (a)欠拟合与过拟合 (b)模型退化

2. 梯度爆炸/消失? Gradient Exploding/Vanishing?
除此之外,最受人认可的原因就是“梯度爆炸/消失(弥散)”了。为了理解什么是梯度弥散,首先回顾一下反向传播的知识。
假设我们现在需要计算一个函数 f(x,y,z)=(x+y)×z,x=−2,y=5,z=−4在时的梯度,那么首先可以做出如下所示的计算图。
将x=−2,y=5,z=−4带入,其中,令 x+y=q,一步步计算,很容易就能得出 f(−2,5,−4)=−12。
这就是前向传播(计算图上部分绿色打印字体与蓝色手写字体),即:

 

 前向传播是从输入一步步向前计算输出,而反向传播则是从输出反向一点点推出输入的梯度(计算图下红色的部分)。

 

 

 

 原谅我字丑……

注:这里的反向传播假设输出端接受之前回传的梯度为1(也可以是输出对输出求导=1)
观察上述反向传播,不难发现,在输出端梯度的模值,经过回传扩大了3~4倍。
这是由于反向传播结果的数值大小不止取决于求导的式子,很大程度上也取决于输入的模值。当计算图每次输入的模值都大于1,那么经过很多层回传,梯度将不可避免地呈几何倍数增长(每次都变成3~4倍,重复上万次,想象一下310000有多大……),直到Nan。这就是梯度爆炸现象。
当然反过来,如果我们每个阶段输入的模恒小于1,那么梯度也将不可避免地呈几何倍数下降(比如每次都变成原来的三分之一,重复一万次就是3-10000),直到0。这就是梯度消失现象。值得一提的是,由于人为的参数设置,梯度更倾向于消失而不是爆炸。
由于至今神经网络都以反向传播为参数更新的基础,所以梯度消失问题听起来很有道理。然而,事实也并非如此,至少不止如此。
我们现在无论用Pytorch还是Tensorflow,都会自然而然地加上Bacth Normalization(简称BN),而BN的作用本质上也是控制每层输入的模值,因此梯度的爆炸/消失现象理应在很早就被解决了(至少解决了大半)。
不是过拟合,也不是梯度消失,这就很尴尬了……CNN没有遇到我们熟知的两个老大难问题,却还是随着模型的加深而导致效果退化。无需任何数学论证,我们都会觉得这不符合常理。等等,不符合常理……
3. 为什么模型退化不符合常理?
按理说,当我们堆叠一个模型时,理所当然的会认为效果会越堆越好。因为,假设一个比较浅的网络已经可以达到不错的效果,那么即使之后堆上去的网络什么也不做,模型的效果也不会变差。
然而事实上,这却是问题所在。“什么都不做”恰好是当前神经网络最难做到的东西之一。
MobileNet V2的论文[2]也提到过类似的现象,由于非线性激活函数Relu的存在,每次输入到输出的过程都几乎是不可逆的(信息损失)。我们很难从输出反推回完整的输入。

 

 Mobilenet v2是考虑的结果是去掉低维的Relu以保留信息

也许赋予神经网络无限可能性的“非线性”让神经网络模型走得太远,却也让它忘记了为什么出发(想想还挺哲学)。这也使得特征随着层层前向传播得到完整保留(什么也不做)的可能性都微乎其微。
用学术点的话说,这种神经网络丢失的“不忘初心”/“什么都不做”的品质叫做恒等映射(identity mapping)。
因此,可以认为Residual Learning的初衷,其实是让模型的内部结构至少有恒等映射的能力。以保证在堆叠网络的过程中,网络至少不会因为继续堆叠而产生退化!
二、深度残差学习 Deep Residual Learning
1. 残差学习 Residual Learning
前面分析得出,如果深层网络后面的层都是是恒等映射,那么模型就可以转化为一个浅层网络。那现在的问题就是如何得到恒等映射了。
事实上,已有的神经网络很难拟合潜在的恒等映射函数H(x) = x。
但如果把网络设计为H(x) = F(x) + x,即直接把恒等映射作为网络的一部分。就可以把问题转化为学习一个残差函数F(x) = H(x) - x.
只要F(x)=0,就构成了一个恒等映射H(x) = x。 而且,拟合残差至少比拟合恒等映射容易得多。
于是,就有了论文[3]中的Residual block结构

 

 Residual Block的结构

图中右侧的曲线叫做跳接(shortcut connection),通过跳接在激活函数前,将上一层(或几层)之前的输出与本层计算的输出相加,将求和的结果输入到激活函数中做为本层的输出。
用数学语言描述,假设Residual Block的输入为 x,则输出 y等于:

 

 其中 F(x,{Wi})是我们学习的目标,即输出输入的残差 y−x。以上图为例,残差部分是中间有一个Relu激活的双层权重,即:

 

 其中 σ指代Relu,而 W1,W2指代两层权重。

顺带一提,这里一个Block中必须至少含有两个层,否则就会出现很滑稽的情况:

 

 显然这样加了和没加差不多……

2.网络结构与维度问题

 

 ResNet结构示意图(左到右分别是VGG,没有残差的PlainNet,有残差的ResNet)

论文中原始的ResNet34与VGG的结构如上图所示,可以看到即使是当年号称“Very Deep”的VGG,和最基础的Resnet在深度上相比都是个弟弟。
可能有好奇心宝宝发现了,跳接的曲线中大部分是实现,但也有少部分虚线。这些虚线的代表这些Block前后的维度不一致,因为去掉残差结构的Plain网络还是参照了VGG经典的设计思路:每隔x层,空间上/2(下采样)但深度翻倍。
也就是说,维度不一致体现在两个层面:
• 空间上不一致
• 深度上不一致
空间上不一致很简单,只需要在跳接的部分给输入x加上一个线性映射 WsW_{s} ,即:

 

 对于深度上的不一致,则有两种解决办法,一种是在跳接过程中加一个1*1的卷积层进行升维,另一种则是直接简单粗暴地补零。事实证明两种方法都行得通。

注:深度上和空间上维度的不一致是分开处理的,但很多人将两者混为一谈(包括目前某乎一些高赞文章),这导致了一些人在模型的实现上感到困惑(比如当年的我)。
3. torchvision中的官方实现
事实上论文中的ResNet并不是最常用的,我们可以在Torchvision的模型库中找到一些很不错的例子,这里拿Resnet18为例:

 

 运行代码:

,而ResNet的衰减却只有 。这也验证了ResNet论文本身的观点,网络训练难度随着层数增长的速度不是线性,而至少是多项式等级的增长(如果该论文属实,则可能是指数级增长的)import torchvision
model = torchvision.models.resnet18(pretrained=False) #我们不下载预训练权重
print(model)
得到输出:
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(1): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer2): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer3): Sequential(
(0): BasicBlock(
(conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer4): Sequential(
(0): BasicBlock(
(conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(avgpool): AvgPool2d(kernel_size=7, stride=1, padding=0)
(fc): Linear(in_features=512, out_features=1000, bias=True)
)
薰风说 Thinkings
上述的内容是我以自己的角度思考作者提出ResNet的心路历程,我比作者蔡很多,所以难免出现思考不全的地方。
ResNet是如此简洁高效,以至于模型提出后还有无数论文讨论“ResNet到底解决了什么问题(The Shattered Gradients Problem: If resnets are the answer, then what is the question?)”[4]
论文[4]认为,即使BN过后梯度的模稳定在了正常范围内,但梯度的相关性实际上是随着层数增加持续衰减的。而经过证明,ResNet可以有效减少这种相关性的衰减。
对于 L层的网络来说,没有残差表示的Plain Net梯度相关性的衰减在 ,
而对于“梯度弥散”观点来说

 

 ,在输出引入一个输入x的恒等映射,则梯度也会对应地引入一个常数1,这样的网络的确不容易出现梯度值异常,在某种意义上,起到了稳定梯度的作用。

除此之外,shortcut类似的方法也并不是第一次提出,之前就有“Highway Networks”。可以只管理解为,以往参数要得到梯度,需要快递员将梯度一层一层中转到参数手中(就像我取个快递,都显示要从“上海市”发往“闵行分拣中心”,闵大荒日常被踢出上海籍)。而跳接实际上给梯度开了一条“高速公路”(取快递可以直接用无人机空投到我手里了),效率自然大幅提高,不过这只是个比较想当然的理由。

 

 上面的理解很多论文都讲过,但我个人最喜欢下面两个理解。

第一个已经由Feature Pyramid Network[5]提出了,那就是跳连接相加可以实现不同分辨率特征的组合,因为浅层容易有高分辨率但是低级语义的特征,而深层的特征有高级语义,但分辨率就很低了。
第二个理解则是说,引入跳接实际上让模型自身有了更加“灵活”的结构,即在训练过程本身,模型可以选择在每一个部分是“更多进行卷积与非线性变换”还是“更多倾向于什么都不做”,抑或是将两者结合。模型在训练便可以自适应本身的结构,这听起来是多么酷的一件事啊!
有的人也许会纳闷,我们已经知道一个模型的来龙去脉了,那么在一个客观上已经十分优秀的模型,强加那么多主观的个人判断有意思吗?
然而笔者还是相信,更多角度的思考有助于我们发现现有模型的不足,以及值得改进的点。比如我最喜欢的两个理解就可以引申出这样的问题“虽然跳接可以结合不同分辨率,但ResNet显然没有充分利用这个优点,因为每个shortcut顶多跨越一种分辨率(大部分还不会发生跨越)”。
那么“如果用跳接组合更多分辨率的特征,模型的效果会不会更好?”这就是DenseNet回答我们的问题了。
参考文献
[1]https://www.coursera.org/learn/machine-learning
[2]Sandler M, Howard A, Zhu M, et al. MobileNetV2: Inverted Residuals and Linear Bottlenecks[J]. 2018.
[3]He K, Zhang X, Ren S, et al. Deep Residual Learning for Image Recognition[J]. 2015.
[4]Balduzzi D , Frean M , Leary L , et al. The Shattered Gradients Problem: If resnets are the answer, then what is the question?[J]. 2017.
[5]Lin T Y , Dollár, Piotr, Girshick R , et al. Feature Pyramid Networks for Object Detection[J]. 2016.
知识图谱嵌入结合图路径推荐系统RippleNet
RippLeNet基础思想
水波网络(RippLeNet)由上海交通大学和微软亚洲在2018年提出。RippLeNet有效地结合了知识图谱嵌入与知识图谱图路径提供的信息。效果很好,模型可解释性也很便于理解。该算法现在是最热门的知识图谱推荐算法之一。
它的基础思想是利用物品的知识图谱数据一层一层地往外扩散后提取节点,然后聚合Embedding,每一层的物品会影响到在它之后的所有层,并且越往外对结果的影响就越小,就像水波一样,如图5-28所示。

 

 ■ 图5-28 RippLeNet水波扩散示意图

这听起来很像是一种图采样。虽然说RippLeNet的提出年份在GCN 与GraphSAGE之后,但是从RippLeNet的论文中可以推理出,作者当时似乎并不是从图神经网络的思想出发,所以RippLeNet相当于从侧面碰撞到了图神经网络。
02 RippLeNet计算过程
首先参看RippLeNet模型的计算总览图,如图5-29所示。

 

 ■ 图5-29 RippLeNet计算总览图

这张图初看之下有点复杂,为了方便理解,先把注意力集中在Item Embedding UserEmbedding和Predicted Probability这三项中,所以先把其余部分遮盖掉,如图5-30所示。

 

 ■ 图5-30 RippLeNet图解(1)

将其余部分先视作黑匣子。这样一来可以理解为,经过一通操作,最后将得到的User Embedding与Item Embedding做某种计算后预测出该User对该Item 的喜爱程度。如何计算有很多方法,这里就先用论文中给出的最简单的计算方式,其实是在最常用的求内积之后套个Sigmoid,公式如下:

 

 V 代表物品向量,随机初始化即可,而用户向量U 的计算方式如下:

 

 公式中的

 

 代表第H 波的输出向量。即图5-31中用方框标记出来的长条所代表的向量。

 

 ■ 图5-31 RippLeNet图解(2)

所以关键是如何得到o 向量,先说第1个o 向量,参看以下公式组:

 

 V 是Item 向量,t 是Tail向量,h 是Head向量,r 是Relation映射矩阵,这些都是模型要学的Embedding。式(5-33)表示的计算过程如图5-32所示。

 

 ■ 图5-32 RippLeNet图解(3)

式(5-33)中的S1u代表User的第一层Ripple Set(第一层水波集,在图5-32中表示为Hop 1)。首先取一定数量的用户历史交互Item,然后由这些Item 作为Head实体通过它们的关系r 找到Tail实体,记作(h,r)→t,所以第二层水波(在图5-29中表示为Hop 2)是将第一层水波得到的Tail实体作为这一层的Head实体从而找到本层对应的Tail实体,以此类推。
公式所表达的含义相当于是对每个Hop的Tail做一个注意力操作,而权重由Head和Relation得到。
之后将上述步骤得到的o 向量作为下一层水波的V 向量,然后重复进行式(5-33) 的计算。重复H 次后,就可以得到H 个o 向量,然后代入式(5-32)得到用户向量,最后通过与物品向量点积得到预测值。
03水波图采样
在水波网络中所谓每一层向外扩散的操作,实际上每一次都会产生笛卡儿乘积数量级的新实体,所以实际操作时需要设定一个值来限定每一次扩散取得新实体的数量上限,假设这个值为n_memory,若某层中(尤其是初始层)的实体数量不足n_memory,则在候选实体中进行有返回地重复采样,以此补足n_memory个新实体。
水波采样与GraphSage略有不同。最普通的GraphSage通常会限定每个节点采样的邻居数量,假设这个值为n,则经过3层采样,则总共采集到的节点数量为n3,而水波网络限定的是每一层采样的邻居总数量为n,即经过3层采样后采集到的节点数量为3×n。对于利用物品图谱作推荐的模型,水波采样的方式有下几点优势:
(1) 统一了每一层中实体的数量,便于模型训练时的batch计算。
(2) 在物品图谱中不需要对每个节点平等对待,位于中心的物品节点及周围的一阶邻居采样被采集的概率较高,而越外层的节点被采集到的概率越低,这反而更合理。水波模型中心节点是该用户历史最近交互的物品实体,而越往外扩散自然应该像水波一般慢慢稀释后续实体的权重。
水波采样的实际操作还需注意的是,训练时每一次迭代应该重新进行一次随机采样,增加训练数据的覆盖率。在做评估时同样也需重新进行一次随机采样,而不是用训练时已经采样好的水波集作为评估时的水波集。
在做预测时,理论上讲用户的Embedding也会根据其历史交互的正例物品通过随机采样的方式进行水波扩散而得到,所以预测时也有随机因子,用户相同的请求也会得到略有不同的推荐列表,但是如果不想有随机因子,则可采取训练时最后采样得到的水波集聚合出的用户Embedding直接用作后续计算。
另外水波采样的层数也有讲究,水波采样的层数最好为最小对称元路径阶数的倍数,下面来慢慢讲解这句话。
元路径的阶数指的是元路径包含的节点类型数量。例如:
元路径为电影→演员→电影,则阶数为3。
元路径为电影→角色→演员→角色→电影,则阶数为5。
水波采样最好通过候选物品的知识图谱扩散出去找到相关的其他目标物品,从而挖掘出推荐物品。假设目前知识图谱的最小对称元路径是电影→演员→电影,即路径阶数为3。如果水波采样层数仅为2,则代表每次迭代根本就没有挖掘出其他电影,而仅采集到演员,所以采样的水波层数需要不小于最小对称元路径阶数。
至于为什么最好是最小对称元路径路径阶数的倍数倒并不是很关键,为倍数的优势是因为这样大概率能在最终一层的水波集节点停留在与候选物品同样实体类型的实体上,实测后得知这对模型的训练有正向影响,但更关键的是要保证水波采样的层数要大于或等于最小对称元路径阶数。


参考文献链接
https://mp.weixin.qq.com/s/LoCJnKa8Ty2ytCbXS2jPow
https://www.zhihu.com/question/64494691/answer/786270699?utm_campaign=shareopn&utm_medium=social&utm_oi=860016607857098752&utm_psn=1573769729172267008&utm_source=wechat_timeline
https://mp.weixin.qq.com/s/R-VmZQ4EJVf6gzyPJ5FKRQ

 

posted @ 2022-11-10 04:57  吴建明wujianming  阅读(368)  评论(0编辑  收藏  举报