用深度學習做圖像分類任務模型訓練經驗

前言

用深度學習做圖像分類任務摸索中踩了很多坑,也總結出了一些經驗。現在將一些自己覺得非常實用的模型訓練經驗寫下來作爲記錄,也方便後來者借鑑驗證。

調參經驗

  • 模型選擇
    通常我會使用一個簡單的CNN模型(這個模型一般包含5個卷積層)將數據扔進去訓練跑出一個baseline,這一步工作主要是爲了驗證數據集的質量。如果這個模型訓練結果很差就不要先調試模型,需要檢查一下你的訓練集數據,看看圖像的質量,圖像標籤是否正確,模型的代碼是否正確等等,否則就是在做無用功,畢竟:garbage in,garbage out
  • 超參數的選擇
    調參是項技術活,調得好CVPR,調不好下海搬磚。
    通常要選的超參數有卷積核大小和數目,批訓練(batch size)大小,優化函數(optimizer),學習率等等,一般來說卷積核用33或者55,batch szie 用16或者32不會過擬合,optimizer用Adam(學習率建議用論文中默認的,我試過調整Adam的學習率,效果或都沒有默認的好),激活函數用relu這個應該是大家的共識吧。還有就是先跑幾百個epoch看loss的變化趨勢。
  • 數據預處理
    訓練數據對模型的影響是決定性的,提高訓練數據的質量,就是在提高模型的準確率。
    圖像預處理的時候一般我會抽出部分圖像觀察,對圖像中的噪聲進行濾波,圖像標籤要驗證一下,其他的預處理就結合實際情況來看了。一般來說,數據清洗的工作佔比是多於寫模型的工作(通常是7:3)。
  • 數據增強
    數據增強已經是訓練深度網絡的常規操作了,這味丹藥有利於增加訓練數據量,減少網絡過擬合程度,男女老少,居家旅行必備良藥。
    常用的數據增強方法包括:圖像縮放,圖像翻轉,圖像裁剪,圖像色彩的飽和度、亮度和對比度變換
    海康威視在ImageNet上曾經用過PCA Jittering的方法,但是由於這個方法的計算量過大,我沒有在自己的訓練中使用過。他們還使用了有監督的數據增強的方法,有興趣的同學可以研究一下。
    在這裏插入圖片描述
  • 數據不平衡的處理
    如果訓練數據中各類樣本數目差距較大,很有可能會導致部分類別的準確率很低,從根本上解決樣本不平衡的問題就是要把樣本變平衡。
    一種是增加樣本少的類別的圖像數目,可以用上述數據增強的方法。
    另一種就是直接將樣本多的類別圖像數目減少,可以說是非常簡單粗暴了。
    當然,也有人提出類別權重的方法,增加少樣本在訓練時的權重,間接地增強了圖像數目。
  • 自己的數據生成器
    當任務變得複雜,數據規模變大時,框架提供的接口不能滿足你的需求,這時你需要有自己的data generation function。例如,我使用keras時需要對輸入圖片進行多標籤任務的訓練,而keras本身不包含這樣的接口,所以需要自己實現一個data generation function。通過查看官方文檔和相關接口實現了一個多標籤數據生成器(寫這個數據生成器的時候被官方文檔坑了一次,暫且不表,下次另起一文詳說),代碼如下:
# 訓練集/測試集數據生成器,替換flow_from_directory()
def flow_from_2DList(directory=None, target_size=(256, 256), 
    color_mode='rgb', classes=None, class_mode='categorical', 
    batch_size=1, shuffle=True, seed=None, save_to_dir=None, 
    save_prefix='', save_format='png', follow_links=False, 
    subset=None, interpolation='nearest'):
    """   
    A DirectoryIterator yielding tuples of (x, y) 
    where x is a numpy array containing a batch of images 
    with shape (batch_size, *target_size, channels) and 
    y is a numpy array of corresponding labels.
    """
    # 每個epoch都要shuffle數據集
    random.shuffle(directory)

    # 參數初始化
    if directory is None:   # python函數的默認參數如果是list這種可變類型,
                            # 需要在函數體內進行初始化,
                            # 否則會在上次的結果後繼續使用list
        directory = [ [ 99999 for x in range(4) ] for y in range(batch_size) ]

    list_len = len(directory)
    print('\nlength of directory:', list_len, '\n\n')
    print('\nbatch_size:', batch_size, '\n\n')
    step = list_len//batch_size   # 向下取整得到一個epoch需要多少個step
    print('\nsetp:',step,'\n\n')

    for i in range(step):
        # 每行一個記錄讀取訓練/測試數據,返回(x,[y1,y2,y3])
        batch_images = []

        y_label_age = np.zeros((batch_size, 100))
        y_label_sex = np.zeros((batch_size, 2))
        y_label_sick = np.zeros((batch_size, 2))

        batch_directory = directory[i*batch_size : (i+1)*batch_size].copy()

        batch_size_num = 0 # 循環計數器

        for record in batch_directory:
            file_path = record[0]
            image = cv2.imread(file_path)
            image = cv2.resize(image, target_size)

            batch_images.append(image)

            age = record[1]
            sex = record[2]
            sick = record[3]

            # 將age,sex,sick轉換成one-hot編碼         
            if age != 0:
                age -= 1
            age = to_categorical(age, num_classes = 100)

            sex = to_categorical(sex-1, num_classes = 2)   
            sick = to_categorical(sick-1, num_classes = 2)

            y_label_age[batch_size_num,:] = age
            y_label_sex[batch_size_num,:] = sex
            y_label_sick[batch_size_num,:] = sick

            batch_size_num += 1

        batch_images = np.array(batch_images)
        y_labels = [y_label_age, y_label_sex, y_label_sick]
        data = (batch_images, y_labels)
        yield data

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