层级分类(续)-使用B-CNN(Branch CNN)实现
一、上回介紹
承接上回多級分類留下的困惑,這一次更新分享一些取得實質的進展!
在上一篇博客中,我嘗試建立這樣一個結構的多層次化的分類模型:
然而實際上我構建的是一個這樣的一個分類模型:
雖然最終的分類效果還不錯,但我也清楚很大程度上是依賴于利用了訓練好的VGG16模型,以及當前分類的數目相對較少。這樣的實現是難以符合實際工程需求的,因為實際上很多情況下要分類的類別數目是成千上萬的,這樣簡單的分類方式再加之以百萬級別的數據,效果可想而知。所以很自然而然地會想到如何去實現一個像上上圖一樣的模型,通過層次化的分類,我認為可以提高整個模型應對更多類別以及更多的數據的能力。之前的疑惑也正在此,如何去實現這么一個層次化的模型?
二、B-CNN(Branch Convolutional Neural Network)
感謝校內老師的點撥!這一次發現了一個好東西,B-CNN(Branch CNN),很大程度上解除了我的疑惑,在此分享一下!
Zhu X , Bain M . B-CNN: Branch Convolutional Neural Network for Hierarchical Classification[J]. 2017.
2.1 B-CNN結構
接下來先通過簡單分享幾個論文的關鍵片段介紹一下B-CNN的關鍵構成!首先直接放出結構圖!
A possible way to embed a hierarchy of classes into a CNN model is to output multiple predictions along the CNN layers as the data flow through, from coarse to fine. In this case, lower layers output coarser predictions while higher layers output finer predictions.
We name it Branch Convolutional Neural Network (B-CNN) as it contains several branch networks along the main convolution workflow to do predictions hierarchically.
即論文中認為要實現一個層次化的分類結構,使其能夠完成從粗類到細類的分類功能,則應該讓模型的lower layers輸出粗類(大類)的分類結果而higher layer輸出細類(小類)的分類結果。而該模型命名為B-CNN的原因也在于其包含了多個輸出分支(如上圖)以實現層次化的分類。
A B-CNN model uses existent CNN components as building blocks to construct a network with internal output branches. The network shown at the bottom in Figure 1a is a traditional convolutional neural network. It can be an arbitrary ConvNet with multiple layers. The middle part in Figure 1a shows the output branch networks of a B-CNN. Each branch net produces a prediction on the corresponding level in the label tree (Figure 1b, shown in same color). On the top of each branch, fully connected layers and a softmax layer are used to produce the output in one-hot representation. Branch nets can consist of ConvNets and fully connected neural networks. But for simplicity, in our experiments, we only use fully connected neural networks asour branch nets.
簡要概括一下,即論文中所構建的B-CNN結構,在圖中水平推進的方向上,(即從input到最后的fine prediction)大多使用的都是ConvNet構成的block,而其中垂直分支出來的部分,為了實驗的方便則采用的是全連接層進行預測輸出。
2.2 Loss Function&Loss Weight
When the image is fed into B-CNN, the network will output three corresponding predictions as the data flow through and each level’s loss will contribute to the final loss function base on the loss weights distribution.
論文中同樣提到了Label-Tree這樣一個概念,即針對不同層次的分類數目,由Coarse 1到最終的Fine(Coarse 3)擁有三種不同one-hot標簽。而當一張圖片輸入進該模型后,整個模型會相應輸出三個與層次對應的預測向量,然后每一個層次輸出的loss將對應一個loss weight(損失權重),而三個層次的loss則根據各自的weight合并構成最終模型的loss。
這里附上論文中所定義的損失函數。簡要的解釋一下,其中K代表的是不同的層次數,A代表的是損失權重(Loss weight),而最后那個logxxx則是交叉熵損失(Cross Entropy Loss)。這樣的一個損失函數能夠考慮到每一個層次的loss及它們各自對最終loss的貢獻影響。
2.3 Branch Training Strategy-分支訓練策略
他來了他來了,他帶著策略走來了。我認為又讓我眼前一亮的就是論文中提出的針對這個分支模型的訓練策略。那么具體來說是怎么樣一個策略呢?
論文中提出,通過調整各層次的loss weight,可以使模型在不同訓練階段(即epoch數目)的訓練有所側重(focus)。簡單來說,比如一個兩層的模型,起初損失權重的分配可以是[0.9,0.1],即讓模型focus于第一個層次的學習,而過了比如30個epoch后,權重分配可以是[0.2,0.8],這樣模型又將focus于第二個層次的學習訓練。
This procedure requires the classifier to extract lower features first with coarse instructions and fine tune parameters with fine instructions later. It to an extent prevents the vanishing gradient problem which would make the updates to parameters on lower layers very difficult when the network is very deep.
如論文所闡述的,這樣一個有所側重分支訓練策略具有可解釋性,并且能夠一定程度上減少深度學習常面對的梯度消失問題。
三、實際實現
ok接下來就針對B-CNN結合之前的代碼和論文代碼進行實現!這里要說明一下原論文代碼在這里->B-CNN
首先是一些必要的包以及預設的一些值如下:
接下來一步是上回提到的,就是將label分別按層次處理成對應的one-hot編碼以及劃分訓練集和驗證集,詳細可以對應上回。
#輸入訓練文件路徑,返回數據array以及labels def prepare(path):fileList = os.listdir(path) # 待修改文件夾data=[]labels1=[]labels2=[]for fileName in fileList:image=cv2.imread(os.path.join(path,fileName))if image is not None:std_image=tf.image.per_image_standardization(image)#圖片normalizationimage2=cv2.resize(std_image.numpy(),Image_size)image2=img_to_array(image2)data.append(image2)label1=str(fileName).split("_")[0:1]label2=str(fileName).split("_")[1:2]labels1.append(label1)labels2.append(label2)data=np.array(data,dtype="float32")return data,labels1,labels2path=r"./DATA/train" data,labels1,labels2=prepare(path) mlb1=MultiLabelBinarizer()#用于生成one-hot編碼 mlb2=MultiLabelBinarizer() labels1_onehot=mlb1.fit_transform(labels1).astype('float32')#one-hot編碼 labels1_num=len(mlb1.classes_) labels2_onehot=mlb2.fit_transform(labels2).astype('float32') labels2_num=len(mlb2.classes_)label=[] for i in range(labels2_onehot.shape[0]):lab=[]lab.append(labels1_onehot[i])lab.append(labels2_onehot[i])label.append(lab)#創建自定義標簽(trainX,testX,trainY,testY)=train_test_split(data,label,test_size=0.2,random_state=42)然后構建一個自定義的Generator用于數據增強,與上回提到的方法一樣:
train_datagen = ImageDataGenerator(#用于數據增強rotation_range=15,shear_range=0.1,rescale=1./255,zoom_range=0.2,horizontal_flip=True,width_shift_range=0.1,height_shift_range=0.1 ) validate_datagen = ImageDataGenerator(rescale=1./255 ) def data_generator(generator,images,labels,batch_size):#自定義generator,輸出多標簽num_samples=len(images)input_generator=generator.flow(images,labels,batch_size=batch_size)while True:for offset in range(0,num_samples,batch_size):batch_samples,batch_labels=input_generator.next()X_train=[]y1_train=[]y2_train=[]for i in range(len(batch_samples)):img=batch_samples[i]labels1=batch_labels[i][0]labels2=batch_labels[i][1]X_train.append(img)y1_train.append(labels1)y2_train.append(labels2)X_train=np.array(X_train)y1_train=np.array(y1_train)y2_train=np.array(y2_train)yield X_train,[y1_train,y2_train]接下來是模型的搭建,其中包含了一個Coarse分支(對應我原先 交通工具-動物的大類),以及一個Fine分支細分。
#------------------model---------------------- img_input = Input(shape=Image_shape, name='input') #--- block 1 --- x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(img_input) x = BatchNormalization()(x) x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x) x = BatchNormalization()(x) x = MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)#--- coarse branch --- c_1_bch = Flatten(name='c1_flatten')(x) c_1_bch = Dense(256, activation='relu', name='c1_fc2')(c_1_bch) c_1_bch = BatchNormalization()(c_1_bch) c_1_bch = Dropout(0.5)(c_1_bch) class_one_classify = Dense(labels1_num, activation='softmax', name='class_one')(c_1_bch)#--- block 3 --- x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x) x = BatchNormalization()(x) x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x) x = BatchNormalization()(x) x = MaxPooling2D((2, 2), strides=(2, 2), name='block3_pool')(x)#--- fine block --- x = Flatten(name='flatten')(x) x = Dense(256, activation='relu', name='fc_cifar10_1')(x) x = BatchNormalization()(x) x = Dropout(0.5)(x) class_two_classify = Dense(labels2_num, activation='softmax', name='class_two')(x)model = Model(input=img_input, output=[class_one_classify, class_two_classify], name='branch-CNN')sgd = optimizers.SGD(lr=0.003, momentum=0.9, nesterov=True) model.compile(loss='categorical_crossentropy',optimizer=sgd,loss_weights=[alpha, beta],metrics=['accuracy'])接下來就是對Keras又進一步的了解,即如何使用callbacks實現可控制的訓練過程:
change_lr = LearningRateScheduler(scheduler)#可控制學習率變化 change_lw = LossWeightsModifier(alpha, beta)#控制損失權重的變化 cbks = [change_lr, change_lw]total_validate=len(testY)#總驗證數 total_train=trainX.shape[0]#總訓練數 history=model.fit_generator(data_generator(train_datagen,trainX,trainY,batch_size),epochs=60,validation_data=data_generator(validate_datagen,testX,testY,batch_size),validation_steps=total_validate//batch_size,steps_per_epoch=total_train//batch_size,callbacks=cbks,verbose=1, )發現callbacks是一個很有趣的東西,通過callbacks可以動態調整訓練參數、提早結束訓練等讓我覺得有意思的操作。打算接下來再仔細學習一下關于callbacks的使用技巧,以深化對keras的運用嘿嘿。
那么以上即為根據B-CNN對于層級分類的具體實現啦!
四、實驗結果
囿于設備有限,我暫時還沒調整出能得到比較好結果的參數。不過接下來會慢慢進行調整,我還是挺相信這個結構能帶來比較好的成果的!待之后調試出比較好的參數再放結果上來嘿嘿。
五、接下來目標
基本上來說,對于自己探索的這一整個流程,已經經歷了從數據搜集、數據預處理、模型構建、模型優化這些個過程,接下來要做的就是模型的部署。這些天在家其實搗鼓了不少,接下來的一篇應該是記錄一下部署的成果!
總結
以上是生活随笔為你收集整理的层级分类(续)-使用B-CNN(Branch CNN)实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM垃圾回收机制学习
- 下一篇: @PathVariable注解使用