初嘗Kaggle系列—Leaf Classification(keras)

    Leaf Classification 是比較老的一個題目了,目前在Kaggle上已經有了很多的優秀kernel,作爲一名課餘時間自學深度學習的學生,拿這道題目來熟悉CNN和Keras,同時寫一下自己在做這道題的過程中遇到的一些問題和自己感悟(PS:private leaderboard score 0.00191)。如若有錯誤或不足的地方,歡迎各位大佬指正。

一、解題思路介紹

    題目中提供了兩種信息,1、原始圖片信息,2、預處理特徵(the provide pre-extracted features:margin、sharp、texture)。第一種方案,我們可以直接利用已有特徵信息,構建常規分類器例如Adaboosting,Svm,Logistic,決策樹等等,或者搭建神經網絡來處理pre-extracted features第二種方案,利用圖片信息搭建卷積神經網絡,當然也可以利用現有的網絡模型。第三種方案就是同時利用圖片信息與預處理特徵,當時看到這個想法的時候(大佬AbhijeetMulgund的思路),就很感興趣,所以就按照這個思路做了(也算是一個及其簡易的modle ensemble吧)。

二、題目代碼

   1、讀取train.csv,test.csv中的信息,讀取image信息,class編碼,這些都可以參考AbhijeetMulgund的代碼(請不要吝嗇你對大佬的vote)。

    2、因爲要搭建CNN網絡,所以利用已有的image肯定是不夠的,需要Data Augmentation,又因爲我們需要將兩種特徵混合到一個網絡之中去,因此不能簡單的調用ImageDataGenerator,這裏我提供兩種方案a、重載class ImageDataGenerator,b、自定義generator。這裏我說明一下,其實自定義generator更簡單一點,但是因爲是初學者,所以我就都寫了一遍。

    a、重載ImageDataGenerator(利用源代碼去改寫,我第一次就是打算自己單擼,各種bug大哭

from keras.preprocessing.image import ImageDataGenerator,NumpyArrayIterator,Iterator
class ImageDataGenerator_leaf(ImageDataGenerator):
    def flow(self, x,pre_feature,y,batch_size=32, 
             shuffle=False, seed=None):
        return NumpyArrayIterator_leaf(
            x,pre_feature,y,self,
            batch_size=batch_size,
            shuffle=shuffle,
            seed=seed)

class NumpyArrayIterator_leaf(Iterator):
    def __init__(self, x,pre_feature, y, image_data_generator,
                 batch_size=32, shuffle=False, seed=None,
                 data_format=None,
                 subset=None)
        if data_format is None:
            data_format = K.image_data_format()
        self.pre_feature = np.asarray(pre_feature)                    #預處理特徵
        if self.x.ndim != 4:
            raise ValueError('Input data in `NumpyArrayIterator` '
                             'should have rank 4. You passed an array '
                             'with shape', self.x.shape)
        self.x = np.asarray(x)                                        #傳入的image信息
        if y is not None:
            self.y = np.asarray(y)
        else:
            self.y = None
        self.image_data_generator = image_data_generator
        self.data_format = data_format
        self.n = len(self.x)
        self.batch_size = batch_size
        self.shuffle  =shuffle
        self.seed = seed
        self.index_generator = self._flow_index()
        super(NumpyArrayIterator_leaf, self).__init__(self.n, batch_size, shuffle, seed)

    def _get_batches_of_transformed_samples(self, index_array):
        batch_x = np.zeros(tuple([len(index_array)] + list(self.x.shape)[1:]),
                           dtype=K.floatx())
        batch_pre_feature = np.zeros(tuple([len(index_array)] +[len(self.pre_feature[0])] ),
                           dtype=K.floatx())
        for i, j in enumerate(index_array):
            x_temp = self.x[j]
            if self.image_data_generator.preprocessing_function:
                x_temp = self.image_data_generator.preprocessing_function(x_temp)
            x_temp = self.image_data_generator.random_transform(x_temp.astype(K.floatx()))
            x_temp = self.image_data_generator.standardize(x_temp)
            batch_x[i] = x_temp
        if self.y is None:
            return batch_x
        batch_pre_feature = self.pre_feature[index_array]
        batch_y = self.y[index_array]
        return [batch_x,batch_pre_feature], batch_y
    def next(self):
        # Keeps under lock only the mechanism which advances
        # the indexing of each batch.
        with self.lock:
            index_array = next(self.index_generator)
        # The transformation of images is not under thread lock
        # so it can be done in parallel
        return self._get_batches_of_transformed_samples(index_array)
imgen = ImageDataGenerator_leaf(
    rotation_range=20,
    zoom_range=0.2,
    horizontal_flip=True,
    vertical_flip=True,
    fill_mode='nearest')
imgen_train = imgen.flow(X_img_tr,X_num_tr, y_tr_cat)

        b、自定義generator

    ①、先定義生成器

imgen = ImageDataGenerator(
    rotation_range=20,
    zoom_range=0.2,
    horizontal_flip=True,
    vertical_flip=True,
    fill_mode='nearest')

    ②、定義自己的generator

def leaf_generator(generator, X_img_tr, X_num_tr, y_tr_cat):
    imgen_train = generator.flow(X_img_tr, y_tr_cat)
    n = (len(X_img_tr)+31)//32     #這裏我使用了默認的batch_size:32,然後根據generator源代碼裏迭代次數的定義
    while True:                    # n = (self.n+batch_size-1)//batch_size 計算n值
        for i in range(n):              
            x_img,y = imgen_train.next()
            x_num = X_num_tr[imgen.index_array[:32]]
            yield [x_img,x_num],y

    ③、在使用fit_generator的時候傳入自定義的leaf_generator

    3、搭建CNN網絡

    可以參考上面鏈接裏的代碼,但是個人感覺這個網絡其實是有一定的問題的,以下是我對這個網絡結構的一些見解,如有錯誤,希望各位看官指正。

    首先我們來看一下網絡的summary:

 

可以看到,進入全連接層之後,圖像提取的特徵值個數是18432,而預處理的特徵值個數是192,這將會導致最終預處理數據所佔的權重過低,甚至是幾乎不起作用,也就失去了這個思路的意義,因此,可以對圖像特徵添加一個dense層來降維,使兩種特徵的權重均衡,或者對預處理特徵用dense層進行維度提升。這裏因爲不想增加網絡複雜度(主要是電腦配置太弱),採用的是降維的方案。

def combined_model():
    image = Input(shape=(96, 96, 1), name='image')
    x = Conv2D(8,kernel_size =(5,5),strides =(1,1),border_mode='same')(image)
    x = (Activation('relu'))(x)
    x = (MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))(x)
    x = Conv2D(32,kernel_size =(5,5),strides =(1,1),border_mode='same')(x)
    x = (Activation('relu'))(x)
    x = (MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))(x)

    x = Flatten()(x)
    x = Dense(192,activation='relu')(x)
    numerical = Input(shape=(192,), name='numerical')
    concatenated = merge([x, numerical], mode='concat')

    x = Dense(100, activation='relu')(concatenated)
    x = Dropout(.5)(x)
    out = Dense(99, activation='softmax')(x)
    model = Model(input=[image, numerical], output=out)
    model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
    return model

    4、訓練網絡

三、總結

    對於這個網絡結構其實可以有很多的改進,例如把它設計成真正的聯合模型(針對這個題目其實沒必要),增加網絡深度等等。作爲初探Kaggle的題目,重心放在整體設計流程上也就可以了。麻雀雖小,五臟俱全,整個流程下來涉及到了DataAugmentation,簡易Model Ensemble,也熟悉了簡易CNN的搭建,也算是有所收穫。












發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章