【飛槳開發者說】王成,深度學習愛好者,淮陰師範學院,研究方向為計算機視覺影像與影片處理。
卷積神經網路的不足之處
膠囊的工作原理
def squash(self,vector): ''' 壓縮向量的函式,類似啟用函式,向量歸一化 Args: vector:一個4維張量 [batch_size,vector_num,vector_units_num,1] Returns: 一個和x形狀相同,長度經過壓縮的向量 輸入向量|v|(向量長度)越大,輸出|v|越接近1 ''' vec_abs = fluid.layers.sqrt(fluid.layers.reduce_sum(fluid.layers.square(vector))) scalar_factor = fluid.layers.square(vec_abs) / (1 + fluid.layers.square(vec_abs)) vec_squashed = scalar_factor * fluid.layers.elementwise_div(vector, vec_abs) return(vec_squashed)
囊間動態路由(精髓所在)
虛擬碼的第一行指明瞭演算法的輸入:低層輸入向量經過矩陣乘法得到的û,以及路由迭代次數r。最後一行指明瞭演算法的輸出,高層膠囊的向量Vj。 第2行的bij是一個臨時變數,存放了低層向量對高層膠囊的權重,它的值會在迭代過程中逐個更新,當開始一輪迭代時,它的值經過softmax轉換成cij。在囊間動態路由演算法開始時,bij的值被初始化為零(但是經過softmax後會轉換成非零且各個權重相等的cij)。 第3行表明第4-7行的步驟會被重複r次(路由迭代次數)。 第4行計算低層膠囊向量的對應所有高層膠囊的權重。bi的值經過softmax後會轉換成非零權重ci且其元素總和等於1。 如果是第一次迭代,所有係數cij的值會相等。例如,如果我們有8個低層膠囊和10個高層膠囊,那麼所有cij的權重都將等於0.1。這樣初始化使不確定性達到最大值:低層膠囊不知道它們的輸出最適合哪個高層膠囊。當然,隨著這一程式的重複,這些均勻分佈將發生改變。 第5行,那裡將涉及高層膠囊。這一步計算經前一步確定的路由係數加權後的輸入向量的總和,得到輸出向量sj。 第7行進行更新權重,這是路由演算法的精髓所在。我們將每個高層膠囊的向量vj與低層原來的輸入向量û逐元素相乘求和獲得內積(也叫點積,點積檢測膠囊的輸入和輸出之間的相似性(下圖為示意圖)),再用點積結果更新原來的權重bi。這就達到了低層膠囊將其輸出傳送給具有類似輸出的高層膠囊的效果,刻畫了向量之間的相似性。這一步驟之後,演算法跳轉到第3步重新開始這一流程,並重復r次。
▲ 點積運算即為向量的內積(點積)運算,
class Capsule_Layer(fluid.dygraph.Layer): def __init__(self,pre_cap_num,pre_vector_units_num,cap_num,vector_units_num): ''' 膠囊層的實現類,可以直接同普通層一樣使用 Args: pre_vector_units_num(int):輸入向量維度 vector_units_num(int):輸出向量維度 pre_cap_num(int):輸入膠囊數 cap_num(int):輸出膠囊數 routing_iters(int):路由迭代次數,建議3次 Notes: 膠囊數和向量維度影響著效能,可作為主調引數 ''' super(Capsule_Layer,self).__init__() self.routing_iters = 3 self.pre_cap_num = pre_cap_num self.cap_num = cap_num self.pre_vector_units_num = pre_vector_units_num for j in range(self.cap_num): self.add_sublayer('u_hat_w'+str(j),fluid.dygraph.Linear(\ input_dim=pre_vector_units_num,output_dim=vector_units_num)) def squash(self,vector): ''' 壓縮向量的函式,類似啟用函式,向量歸一化 Args: vector:一個4維張量 [batch_size,vector_num,vector_units_num,1] Returns: 一個和x形狀相同,長度經過壓縮的向量 輸入向量|v|(向量長度)越大,輸出|v|越接近1 ''' vec_abs = fluid.layers.sqrt(fluid.layers.reduce_sum(fluid.layers.square(vector))) scalar_factor = fluid.layers.square(vec_abs) / (1 + fluid.layers.square(vec_abs)) vec_squashed = scalar_factor * fluid.layers.elementwise_div(vector, vec_abs) return(vec_squashed) def capsule(self,x,B_ij,j,pre_cap_num): ''' 這是動態路由演算法的精髓。 Args: x:輸入向量,一個四維張量 shape = (batch_size,pre_cap_num,pre_vector_units_num,1) B_ij: shape = (1,pre_cap_num,cap_num,1)路由分配權重,這裡將會選取(split)其中的第j組權重進行計算 j:表示當前計算第j個膠囊的路由 pre_cap_num:輸入膠囊數 Returns: v_j:經過多次路由迭代之後輸出的4維張量(單個膠囊) B_ij:計算完路由之後又拼接(concat)回去的權重 Notes: B_ij,b_ij,C_ij,c_ij注意區分大小寫哦 ''' x = fluid.layers.reshape(x,(x.shape[0],pre_cap_num,-1)) u_hat = getattr(self,'u_hat_w'+str(j))(x) u_hat = fluid.layers.reshape(u_hat,(x.shape[0],pre_cap_num,-1,1)) shape_list = B_ij.shape#(1,1152,10,1) split_size = [j,1,shape_list[2]-j-1] for i in range(self.routing_iters): C_ij = fluid.layers.softmax(B_ij,axis=2) b_il,b_ij,b_ir = fluid.layers.split(B_ij,split_size,dim=2) c_il,c_ij,b_ir = fluid.layers.split(C_ij,split_size,dim=2) v_j = fluid.layers.elementwise_mul(u_hat,c_ij) v_j = fluid.layers.reduce_sum(v_j,dim=1,keep_dim=True) v_j = self.squash(v_j) v_j_expand = fluid.layers.expand(v_j,(1,pre_cap_num,1,1)) u_v_produce = fluid.layers.elementwise_mul(u_hat,v_j_expand) u_v_produce = fluid.layers.reduce_sum(u_v_produce,dim=2,keep_dim=True) b_ij += fluid.layers.reduce_sum(u_v_produce,dim=0,keep_dim=True) B_ij = fluid.layers.concat([b_il,b_ij,b_ir],axis=2) return v_j,B_ij def forward(self,x): ''' Args: x:shape = (batch_size,pre_caps_num,vector_units_num,1) or (batch_size,C,H,W) 如果是輸入是shape=(batch_size,C,H,W)的張量, 則將其向量化shape=(batch_size,pre_caps_num,vector_units_num,1) 滿足:C * H * W = vector_units_num * caps_num 其中 C >= caps_num Returns: capsules:一個包含了caps_num個膠囊的list ''' if x.shape[3]!=1: x = fluid.layers.reshape(x,(x.shape[0],self.pre_cap_num,-1)) temp_x = fluid.layers.split(x,self.pre_vector_units_num,dim=2) temp_x = fluid.layers.concat(temp_x,axis=1) x = fluid.layers.reshape(temp_x,(x.shape[0],self.pre_cap_num,-1,1)) x = self.squash(x) B_ij = fluid.layers.ones((1,x.shape[1],self.cap_num,1),dtype='float32')/self.cap_num# capsules = [] for j in range(self.cap_num): cap_j,B_ij = self.capsule(x,B_ij,j,self.pre_cap_num) capsules.append(cap_j) capsules = fluid.layers.concat(capsules,axis=1) return capsules
損失函式
def get_loss_v(self,label): ''' 計算邊緣損失 Args: label:shape=(32,10) one-hot形式的標籤 Notes: 這裡我呼叫Relu把小於0的值篩除 m_plus:正確輸出項的機率(|v|)大於這個值則loss為0,越接近則loss越小 m_det:錯誤輸出項的機率(|v|)小於這個值則loss為0,越接近則loss越小 (|v|即膠囊(向量)的模長) ''' #計算左項,雖然m+是單個值,但是可以透過廣播的形式與label(32,10)作差 max_l = fluid.layers.relu(train_params['m_plus'] - self.output_caps_v_lenth) #平方運算後reshape max_l = fluid.layers.reshape(fluid.layers.square(max_l),(train_params['batch_size'],-1))#32,10 #同樣方法計算右項 max_r = fluid.layers.relu(self.output_caps_v_lenth - train_params['m_det']) max_r = fluid.layers.reshape(fluid.layers.square(max_r),(train_params['batch_size'],-1))#32,10 #合併的時候直接用one-hot形式的標籤逐元素乘算便可 margin_loss = fluid.layers.elementwise_mul(label,max_l)\ + fluid.layers.elementwise_mul(1-label,max_r)*train_params['lambda_val'] self.margin_loss = fluid.layers.reduce_mean(margin_loss,dim=1)
編碼器
class Capconv_Net(fluid.dygraph.Layer): def __init__(self): super(Capconv_Net,self).__init__() self.add_sublayer('conv0',fluid.dygraph.Conv2D(\ num_channels=1,num_filters=256,filter_size=(9,9),padding=0,stride = 1,act='relu')) for i in range(8): self.add_sublayer('conv_vector_'+str(i),fluid.dygraph.Conv2D(\ num_channels=256,num_filters=32,filter_size=(9,9),stride=2,padding=0,act='relu')) def forward(self,x,v_units_num): x = getattr(self,'conv0')(x) capsules = [] for i in range(v_units_num): temp_x = getattr(self,'conv_vector_'+str(i))(x) capsules.append(fluid.layers.reshape(temp_x,(train_params['batch_size'],-1,1,1))) x = fluid.layers.concat(capsules,axis=2) x = self.squash(x) return x
解碼器
效能評估
如果您想詳細瞭解更多飛槳的相關內容,請參閱以下文件。
GitHub:
Gitee: