首頁>技術>

Relation Net 是 CVPR2018的一篇論文,論文連結:https://arxiv.org/abs/1711.06025。

深度學習在視覺識別任務中取得巨大的成功,文章作者指出訓練模型需要大量標註過的圖片,同時需要迭代多次去訓練引數。每當新增新的物體類別需要花費時間去標註,同時有一些新興物體類別和稀有物體類別可能根本不存在大量的標註過的圖片。而人類是隻要很少的認知學習就可實現小樣本(FSL)和無樣本學習(ZSL)。作者舉了一個例子,小孩子只要在一張圖片或一本書裡認識了斑馬,或者只是聽到描述斑馬是一種”條紋馬”,就可以毫無困難的識別出斑馬這種動物。為了解決深度學習中樣本很少模型的分類效果就會很差的問題,同時又受到人類的小樣本和無樣本學習能力帶來的啟發,小樣本學習又恢復了一些熱度。深度學習中的Fine-tune技術可以用於一些樣本比較少的情況,但是在只有一個或者幾個樣本的情況下,即使使用了資料增強和正則化技術,仍然會有過擬合的問題。目前其他的小樣本學習的推理機制又比較複雜,所以論文作者提出了一個可以端到端訓練,並且結構簡單的模型 Relation Net。

在 FSL 任務中,一般將資料集分為 training set /support set /testing set 三個資料集。support set和 testing set有共同的標籤,training set裡面不包含 support set和 testing set的標籤。在 support set 中有 K 個標註過的資料和 C 個不同的類別,則稱作為 C-way K-shot。在訓練的過程中從 training set 中選取 sample set /query set 對應support set/testing set,具體方法後面訓練策略裡會詳細說明。

Relation Network由 embedding model 和 relation model 組成。Relation Network 的核心思想是首先透過embedding model分別提取 support set 和 testing set中影象的特徵圖,然後將特徵圖中代表通道數的維度進行拼接,得到一個新的特徵圖。然後把新的特徵圖送入 relation model 進行運算得到 relation score,這個值代表了兩張圖的相似度。

下圖為5-way 1-shot 的情況下接受1個樣本的網路結構與流程。5張sample set 中的圖片與1張 query set 中的圖片會分別的透過 embedding model 提取特徵並拼接,得到5個新的特徵圖,然後送入 Relation Net 進行計算 relation score,最後會得到一個 one-shot 的向量,分數最高的代表對應的類別。

訓練使用的損失函式也比較簡單,使用均方誤差作為損失函式。公式中 ri,j代表圖片 i與 j 的相似度。yi 與 yj代表圖片的真實標籤。

基於飛槳復現Relation Network

Relation Network 模型結構定義請檢視:

https://github.com/txyugood/paddle_RN_FSL/blob/master/RelationNet.py

下面我將復現的技術細節與各位開發者分享。

1.搭建 Relation Network 網路

模型是由embedding model 和 relation model 兩部分組成,兩個網路都主要由 【Conv+BN+Relu】模組組成,所以首先定義一個 BaseNet類,並在其中實現conv_bn_layer方法,程式碼如下:

class BaseNet:    def conv_bn_layer(self,                      input,                      num_filters,                      filter_size,                      stride=1,                      groups=1,                      padding=0,                      act=None,                      name=None,                      data_format='NCHW'):        n = filter_size * filter_size * num_filters        conv = fluid.layers.conv2d(            input=input,            num_filters=num_filters,            filter_size=filter_size,            stride=stride,            padding=padding,            groups=groups,            act=None,            param_attr=ParamAttr(name=name + "_weights", initializer=fluid.initializer.Normal(0,math.sqrt(2. / n))),            bias_attr=ParamAttr(name=name + "_bias",                                initializer=fluid.initializer.Constant(0.0)),            name=name + '.conv2d.output.1',            data_format=data_format)        bn_name = "bn_" + name        return fluid.layers.batch_norm(            input=conv,            act=act,            momentum=1,            name=bn_name + '.output.1',            param_attr=ParamAttr(name=bn_name + '_scale',                                 initializer=fluid.initializer.Constant(1)),            bias_attr=ParamAttr(bn_name + '_offset',                                initializer=fluid.initializer.Constant(0)),            moving_mean_name=bn_name + '_mean',            moving_variance_name=bn_name + '_variance',            data_layout=data_format)

飛槳支援靜態圖和動態圖兩種網路定義模式,這裡我選用的靜態圖,以上程式碼就是定義了一個卷積神經網路中最經常出現的 conv_bn 層,但要注意的是 batch_norm 層的 momentum 設定為1,實現的效果就是不記錄全域性均值和方差。

具體引數含義如下:

- input:傳入待卷積處理的張量物件

- num_filter:卷積核數量(輸出的卷積結果的通道數)

- filter_size:卷積核尺寸

- stride: 卷積步長

- groups:分組卷積的組數量

- padding:填充大小,這裡設定為0,代表卷積後不填充。

- act:接在 BN 層後的啟用函式,如果為 None,則不使用啟用函式

- name:在運算圖中的物件名稱

接著我們定義 Relation Network 中的 embedding model 部分。

class EmbeddingNet(BaseNet):    def net(self,input):        conv = self.conv_bn_layer(            input=input,            num_filters=64,            filter_size=3,            padding=0,            act='relu',            name='embed_conv1')        conv = fluid.layers.pool2d(            input=conv,            pool_size=2,            pool_stride=2,            pool_type='max')        conv = self.conv_bn_layer(            input=conv,            num_filters=64,            filter_size=3,            padding=0,            act='relu',            name='embed_conv2')        conv = fluid.layers.pool2d(            input=conv,            pool_size=2,            pool_stride=2,            pool_type='max')        conv = self.conv_bn_layer(            input=conv,            num_filters=64,            filter_size=3,            padding=1,            act='relu',            name='embed_conv3')        conv = self.conv_bn_layer(            input=conv,            num_filters=64,            filter_size=3,            padding=1,            act='relu',            name='embed_conv4')        return conv

首先建立一個EmbeddingNet類,繼承BaseNet類,它就繼承了conv_bn_layer方法。在EmbeddingNet中定義 net方法,它的引數 input 代表輸入的影象張量,這個方法用來建立網路的靜態圖。輸入的 input 首先經過一個【Conv+BN+relu】的模組得到特徵圖embed_conv1,然後進行了一次最大值池化操作。池化的作用的是在保留重要特徵的前提下縮小特徵圖,後面的卷積和池化操作作用與此相同。最後embed_conv4輸出的特徵圖形狀是[-1,64,19,19]一共4個維度,第1個緯度代表了 batch_size,因為 batch_size 在建立靜態網路時是不確定的,所以用-1來表示可以是任意值。第2個緯度代表了特徵的圖的通道數,經過 embedding model後,特徵圖的通道數為64。最後第3和第4個維度代表了特徵圖的寬度和高度,這裡是19x19。

Relation model 程式碼部分如下:

class RelationNet(BaseNet):    def net(self, input, hidden_size):        conv = self.conv_bn_layer(            input=input,            num_filters=64,            filter_size=3,            padding=0,            act='relu',            name='rn_conv1')        conv = fluid.layers.pool2d(            input=conv,            pool_size=2,            pool_stride=2,            pool_type='max')        conv = self.conv_bn_layer(            input=conv,            num_filters=64,            filter_size=3,            padding=0,            act='relu',            name='rn_conv2')        conv = fluid.layers.pool2d(            input=conv,            pool_size=2,            pool_stride=2,            pool_type='max')        fc = fluid.layers.fc(conv,size=hidden_size,act='relu',                             param_attr=ParamAttr(name='fc1_weights',                                                  initializer=fluid.initializer.Normal(0,0.01)),                             bias_attr=ParamAttr(name='fc1_bias',                                                 initializer=fluid.initializer.Constant(1)),                             )        fc = fluid.layers.fc(fc, size=1,act='sigmoid',                             param_attr=ParamAttr(name='fc2_weights',                                                  initializer=fluid.initializer.Normal(0,0.01)),                             bias_attr=ParamAttr(name='fc2_bias',                                                 initializer=fluid.initializer.Constant(1)),                             )        return fc

建立一個RelationNet類,它同樣繼承於 BaseNet 類,繼承了conv_bn_layer方法。在 net 方法中,模型的前面幾層與 embeding model 中類似使用【Conv+BN+Relu】模組進行特徵提取,在最後使用兩層全連線層,將特徵值對映為一個標量relation score,代表了兩個圖片的相似度。

在訓練過程中,sample set 中圖片和 query set 的圖片經過 embedding model後都得到了形狀為[-1,64,19,19]的特徵圖,再送入 relation model 之前需要進行拼接,這段程式碼略有些複雜,下面我分段解釋一下。

sample_image = fluid.layers.data('sample_image', shape=[3, 84, 84], dtype='float32')query_image = fluid.layers.data('query_image', shape=[3, 84, 84], dtype='float32')         sample_query_image = fluid.layers.concat([sample_image, query_image], axis=0)sample_query_feature = embed_model.net(sample_query_image)

這部分程式碼是將 sample image和 query image的張量在batch_size 的緯度上拼接得到張量sample_query_image,一起送到 embedding model 中去提取特徵,得到sample_query_feature。

sample_batch_size = fluid.layers.shape(sample_image)[0]query_batch_size = fluid.layers.shape(query_image)[0]

這部分程式碼取 image 張量的0維度作為 batch_size。

sample_feature = fluid.layers.slice(                sample_query_feature,                axes=[0],                starts=[0],                ends=[sample_batch_size])if k_shot > 1:# few_shot      sample_feature = fluid.layers.reshape(sample_feature, shape=[c_way, k_shot, 64, 19, 19])      sample_feature = fluid.layers.reduce_sum(sample_feature, dim=1)query_feature = fluid.layers.slice(      sample_query_feature,      axes=[0],      starts=[sample_batch_size],      ends=[sample_batch_size + query_batch_size])

由於之前圖片進行了拼接,所以在特徵之後,同樣需要在sample_query_feature的 batch_size 對應的0維度上進行切片,分別得到sample_feature 和query_feature。這裡如果 K-shot 大於1時,需要對 sample_feature改變形狀,然後在 K-shot 對應的1維度上對 K-shot 個張量求和並刪除該維度,這時 sample_feature的形狀就變成為[C-way,64,19,19]。這時 sample_batch_size 的值應該為 C-way。

sample_feature_ext = fluid.layers.unsqueeze(sample_feature, axes=0)query_shape = fluid.layers.concat(       [query_batch_size, fluid.layers.assign(np.array([1, 1, 1,1]).astype('int32'))])sample_feature_ext = fluid.layers.expand(sample_feature_ext, query_shape)

因為 sample set 中的每一張圖片特徵都需要與 C 個型別的圖片特徵進行拼接,所以這裡透過unsqueeze新增一個維度。根據 expand 介面的引數要求,這裡新建一個 query_shape 張量實現複製 sample_feature 張量query_batch_size 次得到一個形狀為[query_batch_size, sample_batch_size, 64, 19, 19]的張量。

query_feature_ext = fluid.layers.unsqueeze(query_feature, axes=0)if k_shot > 1:    sample_batch_size = sample_batch_size / float(k_shot)sample_shape = fluid.layers.concat(      [sample_batch_size, fluid.layers.assign(np.array([1, 1, 1, 1]).astype('int32'))])query_feature_ext = fluid.layers.expand(query_feature_ext, sample_shape)

同上面的操作一樣,query set 的特徵也需要新增一維度,這裡需要複製 sample_batch_size 次。值得注意的是,如果 k-shot 大於1的情況下,因為之前已經做過 reduce_mean 操作,所以要使sample_batch_size除以 k-shot得到新的sample_batch_size。最後透過複製得到一個[sample_batch_size, query_batch_size, 64, 19, 19]的張量。

query_feature_ext = fluid.layers.transpose(query_feature_ext, [1, 0, 2, 3, 4])relation_pairs = fluid.layers.concat([sample_feature_ext, query_feature_ext], axis=2)relation_pairs = fluid.layers.reshape(relation_pairs, shape=[-1, 128, 19, 19])

最後透過transpose方法進行轉置使sample_feature_ext和query_feature_ext形狀一致,最後對兩個特徵進行拼接和修改形狀得到一個形狀為[query_batch_size x sample_batch_size, 128, 19, 19]的張量relation_pairs。

relation = RN_model.net(relation_pairs, hidden_size=8)relation = fluid.layers.reshape(relation, shape=[-1, c_way])	

最後將之前拼接的特徵送入 relation model 模組,首先會得到一個query_batch_size x sample_batch_size長度的向量,然後改變形狀得到[query_batch_size, sample_batch_size]的張量(sample_batch_size 實際上等於 C-way), sample_batch_size長度的向量以 one-hot 的形式表示出每一個 query image 的類別。

損失函式的程式碼如下:

one_hot_label = fluid.layers.one_hot(query_label, depth=c_way)loss = fluid.layers.square_error_cost(relation, one_hot_label)loss = fluid.layers.reduce_mean(loss)

首先將 query image 的標籤 query_label 轉換為 one-hot 的形式,之前得到的relation也是 one-hot的形式, 然後計算relation和one_hot_label的MSE得到損失函式。

2.訓練策略

在 FSL 任務中,如果只使用 support set 去訓練,也可以對 testing set 進行推理預測,但是由於 support set 中樣本數量比較少,所以分類器的效能一般不好。所以一般使用training set進行訓練,這樣分類器會有一個比較好的效能。這裡有一個有效的方法,叫做 episode based training。

episode based training的實現步驟如下:

- 訓練需要迴圈迭代 N 個 episode,每1個 episode 會在 training set 中隨機選取 C 個類別的中的 K 個數據,組成1個sample set資料集。C和 K 對應 support set 中的 C-way K-shot,一共有 C x K個樣本。

- 然後在 C 個類別中剩餘的樣本中隨機選取幾個樣本作為 query set, 進行訓練。

對於 5-way 1-shot學習,sample set 的 batch_size 選擇的是5,query set 的 batch_size 選擇的是15。對於5-way 5-shot學習,sample set 的 batch_size 選擇的是25(每個類別5張圖),query set 的 batch_size 選擇的10。

對於訓練的最佳化器,選擇的是 Adam最佳化,學習率設定為0.001。

對於資料增廣,在資料讀取時對 sample set 和 query set 的影象都使用了 AutoAugment 的方法來增加資料的多樣性。

3.模型復現效果

驗證時的資料集只使用了論文中實驗用的 minImageNet,共有100個分類,每個分類600張圖片。這個100個分類分別劃分為 training/validation/testing 三個資料集,數量分別為64、16和20。

文章中提到模型在minImageNet的testing 資料集上準確率如下:

Relation Net 在5-way 1-shot 和 5-way 5-shot 分別達到了50.44和65.32左右的準確率。

同樣使用基於飛槳實現的 Relation Net 在minImageNet的testing 資料集上的

5-way 1-shot 準確率:

5-way 5-shot 準確率:

與論文中的準確率一致,模型復現完成。

程式碼地址:https://github.com/txyugood/paddle_RN_FSL

《神經網路與深度學習》中英文高畫質pdf開放下載

19
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 基於病理不變的機器翻譯測試