題目:NetVLAD: CNN architecture for weakly supervised place recognition

這是一篇場景識別的論文,場景識別可以看作是影象檢索的一種。影象檢索是給定query image

q

,然後透過特徵提取器

f

得到一個固定維度的特徵向量

f(q)

,透過計算

f(q)

和資料庫中已提前計算的特徵向量之間的距離(例如歐式距離),從而判斷

q

所屬的類別。

NetVLAD是VLAD的改進版,NeXtVLAD又是NetVLAD的改進版,關於NeXTVLAD的介紹可以參考

如果在閱讀本文的過程中有不理解的地方,可以直接跳到第四部分,對照著NetVLAD的實現再看公式,可能更容易理解NetVLAD是怎麼做的。

1 介紹

論文中提出了一個用於場景識別的可端到端訓練的CNN架構,架構的主要元件是NetVLAD。

下面的這個圖,簡單描述了NetVLAD的聚類思路,如果暫時不理解,沒關係,可以先往下看。

NetVLAD

2 回顧VLAD

通常,透過傳統方法(例如SIFT)會獲得一張影象的多個區域性特徵。Vector of Locally Aggregated Descriptors (VLAD)是其中一種將若干區域性特徵壓縮為一個特定大小全域性特徵的方法,透過聚類,實現了將特徵降維。VLAD在影象檢索任務上得到廣泛使用。

N

D

維的區域性特徵

x_i

作為輸入,

K

個聚類中心

c_k

作為VLAD的引數,輸出的特徵

V

K\times D

維的,這裡為了方便表示把

V

表示成矩陣的形式,實際上VLAD的輸出

V

會被轉換成向量,然後再進行規範化(normalization)操作,最後這個特徵作為影象的全域性特徵。

V_{jk}

的計算公式如下:

V(j,k)=\sum_{i=1}^{N}{\alpha_k(x_i)(x_i(j)-c_k(j))}\\

其中

x_i(j)

表示第

i

個區域性特徵第

j

維的值,

c_k(j)

表示第

k

個聚類中心第

j

維的值。

a_k(x_i)

是指示函式,表示第

i

個區域性特徵是否屬於第

k

類,如果

x_i

與第

k

個聚類中心最近,則

a_k(x_i)

的值為1,則與其他聚類中心(

[1\cdot\cdot\cdot k-1 \cdot\cdot\cdot k+1\cdot\cdot\cdot K]

)的值為0。更具體點,

V(j,k)

計算的是屬於某一類的所有區域性特徵與對應聚類中心的殘差和。

對特徵

V

D

這一維上執行

intra-normalization

操作,再轉換成向量,最後執行

L2-normalized

得到最終的全域性特徵向量。

3 NetVLAD

然而,直接在可訓練的CNN架構中嵌入VLAD層是不可行的,因為VLAD中的

\alpha_k(x_i)

是不連續的,因此論文作者對

\alpha_k(x_i)

做了修改,提出了NetVLAD。

把VLAD中的指示函式

\alpha_k(x_i)

換成可導的

\bar{\alpha}_k(x_i)

\bar{\alpha}_k(x_i)=\frac{e^{-\alpha ||x_i-c_k||^2}}{\sum_{{k^{

約掉分子分母中的

e^{-\alpha ||x_i||^2}

,再令

w_k=2\alpha c_k

b_k=-\alpha ||c_k||^2

,即可得到

\bar{\alpha}_k(x_i)

的最終表示式

\bar{\alpha}_k(x_i)=\frac{e^{w_k^Tx_i+b_k}}{\sum_{k^{

\bar{\alpha}_k(x_i)

表示把

x_i

分配給聚類中心

c_k

的權重,取值範圍為0到1之間。

\bar{\alpha}_k(x_i)

越大表示

x_i

c_k

的距離越近。

\alpha

是一個常數,當

\alpha\rightarrow+\infty

時,

\bar{\alpha}_k(x_i)

的取值就無限接近於1或0了。

最後得到

V(j,k)

的表示式

V(j,k)=\sum_{i=1}^{N}{\frac{e^{w_k^Tx_i+b_k}}{\sum_{k^{

其中

w_k,b_k,c_k

均為可學習的引數。

至此,NetVLAD的推導過程就介紹完了。

4 NetVLAD實現

NetVLAD

下面是NetVLAD層的PyTorch實現,來源於

https://

github。com/lyakaap/NetV

LAD-pytorch/blob/master/netvlad。py

import

torch

import

torch。nn

as

nn

import

torch。nn。functional

as

F

class

NetVLAD

nn

Module

):

“”“NetVLAD layer implementation

Args:

num_clusters : int

The number of clusters

dim : int

Dimension of descriptors

alpha : float

Parameter of initialization。 Larger value is harder assignment。

normalize_input : bool

If true, descriptor-wise L2 normalization is applied to input。

”“”

def

__init__

self

num_clusters

=

64

dim

=

128

alpha

=

100。0

normalize_input

=

True

):

super

()

__init__

()

self

num_clusters

=

num_clusters

self

dim

=

dim

self

alpha

=

alpha

self

normalize_input

=

normalize_input

self

conv

=

nn

Conv2d

dim

num_clusters

kernel_size

=

1

1

),

bias

=

True

self

centroids

=

nn

Parameter

torch

rand

num_clusters

dim

))

def

forward

self

x

):

# x: (N, C, H, W), H * W對應論文中的N,表示區域性特徵的數目,C對應論文中的D,表示特徵的維度

N

C

=

x

shape

[:

2

if

self

normalize_input

x

=

F

normalize

x

p

=

2

dim

=

1

# across descriptor dim

# soft-assignment

soft_assign

=

self

conv

x

view

N

self

num_clusters

-

1

# (N, C, H, W) -> (N, num_clusters, H, W) -> (N, num_clusters, H * W)

soft_assign

=

F

softmax

soft_assign

dim

=

1

# (N, num_clusters, H * W)

x_flatten

=

x

view

N

C

-

1

# (N, C, H, W) -> (N, C, H * W)

# calculate residuals to each clusters

# 減號前面前記為a,後面記為b, residual = a - b

# a: (N, C, H * W) -> (num_clusters, N, C, H * W) -> (N, num_clusters, C, H * W)

# b: (num_clusters, C) -> (H * W, num_clusters, C) -> (num_clusters, C, H * W)

# residual: (N, num_clusters, C, H * W)

residual

=

x_flatten

expand

self

num_clusters

-

1

-

1

-

1

permute

1

0

2

3

-

\

self

centroids

expand

x_flatten

size

-

1

),

-

1

-

1

permute

1

2

0

unsqueeze

0

# soft_assign: (N, num_clusters, H * W) -> (N, num_clusters, 1, H * W)

# (N, num_clusters, C, H * W) * (N, num_clusters, 1, H * W)

residual

*=

soft_assign

unsqueeze

2

vlad

=

residual

sum

dim

=-

1

# (N, num_clusters, C, H * W) -> (N, num_clusters, C)

vlad

=

F

normalize

vlad

p

=

2

dim

=

2

# intra-normalization

vlad

=

vlad

view

x

size

0

),

-

1

# flatten vald: (N, num_clusters, C) -> (N, num_clusters * C)

vlad

=

F

normalize

vlad

p

=

2

dim

=

1

# L2 normalize

return

vlad