我们能比卷积神经网络做得更好吗?
作者|Boris Knyazev
译者 | Jhonson
来源 | Medium
英国机器视觉会议(BMVC)大约两周前在英国卡迪夫结束,是计算机视觉和模式识别领域的顶级会议之一,具有28%的竞争接受率。与其他人相比,这是一个小活动,所以你有足够的时间在会议上走来走去,和论文讲述者一对一的交流,我觉得这大有裨益。
我在会议上展示了一份关于分层多图网络图像分类的工作,在林晓、穆罕默德·艾默尔(主页)和我的博士顾问格雷厄姆·泰勒的监督下,我在SRI国际公司实习期间主要在上面工作。
在本文中,我们基本上试图回答以下问题:“我们能比卷积神经网络做得更好吗?”。 在这里,我讨论这个问题,并通过结果支持我的论点。 我还将引导您使用PyTorch从PASCAL VOC 2012的整个管道前进,获取单个图像。
这篇文章的完整代码在我的Github笔记本(https://github.com/bknyaz/bmvc_2019)上。应该很容易地对其进行调整,以对整个PASCAL数据集进行训练和验证。
那么,为什么我们要比ConvNets做得更好?他们在许多任务上都胜过人类吗?
例如,您可以说图像分类是解决的任务。 好吧,就ImageNet而言,是的。 但是,尽管ImageNet做出了巨大贡献,但这是一个奇怪的任务。 您为什么要区分数百种狗? 因此,结果是我们成功地建立了模型,但是无法区分稍微旋转的狗和猫。 幸运的是,我们现在有了ImageNet-C和其他类似的基准,表明我们离解决它还很遥远。
相关任务(例如对象检测)中出现的另一个未解决的问题是在非常大的图像(例如4000×3000)上进行训练,例如Katharopoulos&Fleuret(ICML,2019)和Ramapuram等人解决了这个问题。 (BMVC,2019)。 多亏了后者,我现在知道如果海报的背景是黑色的,那么很有可能来自Apple。 我也应该保留一些颜色!
因此,也许我们需要不同于卷积神经网络的东西?也许我们应该从一开始就使用具有更好属性的模型,而不是不断修补其错误.
我们认为这种模型可以是图神经网络(GNN):一种可以从图结构数据中学习的神经网络。 GNN具有一些吸引人的属性。 例如,与ConvNets相比,GNN本质上是旋转和平移不变的,因为在图形中根本没有旋转或平移的概念,即没有左右,在某种意义上只有“邻居”(Khasanova和Frossard, ICML,2017)。 因此,人们多年来一直试图解决的使ConvNet更好地推广到不同的轮换的问题可以通过GNN自动解决!
关于从大图像中学习,如何从图像中提取超像素并将低维输入输入到GNN而不是将下采样(例如224×224)图像输入到ConvNet? 与双线性插值相比,超像素似乎是对图像进行下采样的一种更好的方法,因为超像素通常通过保持对象之间的边界来保留很多语义。 借助ConvNet,我们无法直接从这种输入中学习,但是,有一些很好的提议建议利用它们(Kwak等人,AAAI,2017)。
因此,GNN听起来很棒!让我们看看它在实践中的表现。
但是不好了!基于(Kipf&Welling,ICLR,2017)的基准GNN在PASCAL上仅达到19.2%(平均平均精度或mAP),而在每一层中具有相同数量的层和滤波器的ConvNet为32.7%
我们提出了一些改进措施,最终击败了ConvNet!
1. 层次图
在ConvNets中,图像的层次结构是通过池化层隐式建模的。 在GNN中,您至少可以通过两种方式实现这一目标。 首先,您可以使用类似于ConvNets的池化方法,但是对于图而言,定义一种快速且良好的池化方法确实具有挑战性。 相反,我们可以计算多个比例的超像素,并通过将它们与较大的父超像素对应来合并超像素。 但是,由于某些原因,这种合并在我们的案例中效果不佳(我仍然认为效果很好)。 因此,我们改为在输入级别对层次结构建模。 特别是,我们将所有比例的超像素组合成一个集合,并基于语义分割中常用的基于联合的交集(IoU)计算层次关系。
基于该原理,我在下面的代码中构建了层次图。 我还构建了空间图的多尺度版本,但它仅编码空间关系,而IoU应该更好地编码分层关系。 例如,使用IoU,我们可以在远程子节点之间创建快捷方式,即连接两个空间上相距较远但属于同一父节点(例如汽车)的小超像素(例如车轮),如上图所示。
实际上,层次图将mAP提升到31.7%,使其比ConvNet仅低1%,而可训练参数却减少了4倍!如果仅使用空间多尺度图,则结果将比本文中探讨的要差得多。
def compute_iou_binary(seg1, seg2):
inters = float(np.count_nonzero(seg1 & seg2))
#区域可以预先计算
seg1_area = float(np.count_nonzero(seg1))
seg2_area = float(np.count_nonzero(seg2))
return inters / (seg1_area + seg2_area - inters)
def hierarchical_graph(masks_multiscale, n_sp_actual, knn_graph=32):
n_sp_total = np.sum(n_sp_actual)
A = np.zeros((n_sp_total, n_sp_total))
for level1, masks1 in enumerate(masks_multiscale):
for level2, masks2 in enumerate(masks_multiscale[level1+1:]):
for i, mask1 in enumerate(masks1):
for j, mask2 in enumerate(masks2):
A[np.sum(n_sp_actual[:level1], dtype=np.int) + i,
np.sum(n_sp_actual[:level2+level1+1], dtype=np.int) + j] = compute_iou_binary(mask1, mask2)
sparsify_graph(A, knn_graph)
return A + A.T
n_sp_actual = []
avg_values_multiscale, coord_multiscale, masks_multiscale = [], [], []
# Scales [1000, 300, 150, 75, 21, 7] ]在论文中
for i, (name, sp) in enumerate(zip(['children', 'parents', 'grandparents'], [1000, 300, 21])):
superpixels = slic(img, n_segments=sp)
n_sp_actual.append(len(np.unique(superpixels)))
avg_values_, coord_, masks_ = superpixel_features(img, superpixels)
avg_values_multiscale.append(avg_values_)
coord_multiscale.append(coord_)
masks_multiscale.append(masks_)
A_spatial_multiscale = spatial_graph(np.concatenate(coord_multiscale), img.shape[:2], knn_graph=knn_graph)
A_hier = hierarchical_graph(masks_multiscale, n_sp_actual, knn_graph=None)
很棒!我们还可以做些什么来进一步改善结果?
2.易学的关系
到目前为止,如果我们可视化滤波器,它们将看起来非常原始(就像高斯一样)。有关更多详细信息,请参见我的GNN教程(https://medium.com/@BorisAKnyazev/tutorial-on-graph-neural-networks-for-computer-vision-and-beyond-part-1-3d9fada3b80d)。我们想学习一些类似于ConvNets的边缘检测器,因为效果很好。但是事实证明,使用GNN来学习它们非常困难。为此,我们基本上需要根据坐标之间的差异在超像素之间生成边缘。这样,我们将使GNN能够理解坐标系(旋转,平移)。我们将使用在PyTorch中定义的2层神经网络,如下所示:
```python
class GraphLayerFusion(GraphLayerMultiscale):
def __init__(self,
in_features,
out_features,
K,
fusion='pc',
n_hidden=64,
bnorm=True,
activation=nn.ReLU(True),
n_relations=1):
super(GraphLayerFusion, self).__init__(in_features, out_features, K, bnorm, activation, n_relations)
self.fusion = fusion
if self.fusion == 'cp':
fc = [nn.Linear(in_features * K * n_relations, n_hidden),
nn.ReLU(True),
nn.Linear(n_hidden, out_features)]
else:
if self.fusion == 'pc':
fc = [nn.Linear(n_hidden * n_relations, out_features)]
elif self.fusion == 'sum':
fc = [nn.Linear(n_hidden, out_features)]
else:
raise NotImplementedError('cp, pc or sum is expected. Use GraphLayer for the baseline concatenation fusion')
self.proj = nn.ModuleList([nn.Sequential(nn.Linear(in_features * K, n_hidden), nn.Tanh())
for rel in range(n_relations)]) # projection layers followed by nonlinearity
if bnorm:
fc.append(BatchNorm1d_GNN(out_features))
if activation is not None:
fc.append(activation)
self.fc = nn.Sequential(*fc)
def relation_fusion(self, x, A):
B, N = x.shape[:2]
for rel in range(self.n_relations):
y = self.chebyshev_basis(A[:, :, :, rel], x, self.K).view(B, N, -1) # B,N,K,C
if self.fusion in ['pc', 'sum']:
y = self.proj[rel](y) # projection
if self.fusion == 'sum':
y_out = y if rel == 0 else y_out + y
continue
# for CP and PC
if rel == 0:
y_out = []
y_out.append(y)
y = self.fc(y_out if self.fusion == 'sum' else (torch.cat(y_out, 2))) # B,N,F
return y
```
结语
事实证明,有了多关系图网络和一些技巧,我们可以比卷积神经网络做得更好!
不幸的是,在改进GNN的过程中,我们逐渐失去了不变性。例如,旋转图像后,超像素的形状可能会发生变化,而我们用于节点特征以改善模型的超像素坐标也使其健壮性降低。
尽管如此,我们的工作只是迈向更好的图像推理模型的一小步,并且我们证明了GNN可以为一个有希望的方向铺平道路。
有关实现的详细信息,请参阅我在Github上的笔记本:(https://github.com/bknyaz/bmvc_2019)
我还高度推荐Matthias Fey的硕士论文,其中包含与非常相关的主题相关的代码:(https://github.com/rusty1s/embedded_gcnn)
你也许还想看:
● Python Seaborn综合指南,成为数据可视化专家
● 使用Python创建自己的Instagram滤镜|视觉实战
欢迎扫码关注:
原创文章,作者:fendouai,如若转载,请注明出处:https://panchuang.net/2019/11/09/0049c1d913/