深度學習中的數據增強與實現

1. 數據增強Data Augmentation

  • 數據增強讓有限的數據產生更多的數據,增加訓練樣本的數量以及多樣性(噪聲數據),提升模型魯棒性。神經網絡需要大量的參數,許許多多的神經網路的參數都是數以百萬計,而使得這些參數可以正確工作則需要大量的數據進行訓練,但在很多實際的項目中,我們難以找到充足的數據來完成任務。
  • 隨機改變訓練樣本可以降低模型對某些屬性的依賴,從而提高模型的泛化能力
    1. 例如,我們可以對圖像進行不同方式的裁剪,讓物體以不同的實例出現在圖像的不同位置,這同樣能夠降低模型對目標位置的敏感性。
    2. 例如,我們也可以調整亮度、對比度、飽和度和色調 等因素來降低模型對 色彩的敏感度。

2. 數據增強的分類

數據增強可以分為兩類,一類是離線增強,一類是在線增強。

  1. 離線增強 : 直接對數據集進行處理,數據的數目會變成增強因子乘以原數據集的數目,這種方法常常用于數據集很小的時候。

  2. 在線增強 : 這種增強的方法用于,獲得 batch 數據之后,然后對這個 batch 的數據進行增強,如旋轉、平移、翻折等相應的變化,由于有些數據集不能接受線性級別的增長,這種方法長用于大的數據集,很多機器學習框架已經支持了這種數據增強方式,并且可以使用 GPU 優化計算。

3. 數據增強實現

數據增強一般是圖像用的多,都是一些常用的方法,比如random crop,隨機反轉,隨機對比度增強,顏色變化等等,一般來講隨機反轉和一個小比例的random resize,再接random crop比較常用。NLP中將字和詞連接起來就形成了一個新樣本,也屬于數據增強。

圖片數據增強通常只是針對訓練數據,對于測試數據則用得較少。

3.1 PIL 實現

# -*- coding:utf-8 -*-
"""數據增強
   1. 翻轉變換 flip
   2. 隨機修剪 random crop
   3. 色彩抖動 color jittering
   4. 平移變換 shift
   5. 尺度變換 scale
   6. 對比度變換 contrast
   7. 噪聲擾動 noise
   8. 旋轉變換/反射變換 Rotation/reflection
   author: XiJun.Gong
   date:2016-11-29
"""

from PIL import Image, ImageEnhance, ImageOps, ImageFile
import numpy as np
import random
import threading, os, time
import logging

logger = logging.getLogger(__name__)
ImageFile.LOAD_TRUNCATED_IMAGES = True


class DataAugmentation:
    """
    包含數據增強的八種方式
    """


    def __init__(self):
        pass

    @staticmethod
    def openImage(image):
        return Image.open(image, mode="r")

    @staticmethod
    def randomRotation(image, mode=Image.BICUBIC):
        """
         對圖像進行隨機任意角度(0~360度)旋轉
        :param mode 鄰近插值,雙線性插值,雙三次B樣條插值(default)
        :param image PIL的圖像image
        :return: 旋轉轉之后的圖像
        """
        random_angle = np.random.randint(1, 360)
        return image.rotate(random_angle, mode)

    @staticmethod
    def randomCrop(image):
        """
        對圖像隨意剪切,考慮到圖像大小范圍(68,68),使用一個一個大于(36*36)的窗口進行截圖
        :param image: PIL的圖像image
        :return: 剪切之后的圖像

        """
        image_width = image.size[0]
        image_height = image.size[1]
        crop_win_size = np.random.randint(40, 68)
        random_region = (
            (image_width - crop_win_size) >> 1, (image_height - crop_win_size) >> 1, (image_width + crop_win_size) >> 1,
            (image_height + crop_win_size) >> 1)
        return image.crop(random_region)

    @staticmethod
    def randomColor(image):
        """
        對圖像進行顏色抖動
        :param image: PIL的圖像image
        :return: 有顏色色差的圖像image
        """
        random_factor = np.random.randint(0, 31) / 10.  # 隨機因子
        color_image = ImageEnhance.Color(image).enhance(random_factor)  # 調整圖像的飽和度
        random_factor = np.random.randint(10, 21) / 10.  # 隨機因子
        brightness_image = ImageEnhance.Brightness(color_image).enhance(random_factor)  # 調整圖像的亮度
        random_factor = np.random.randint(10, 21) / 10.  # 隨機因1子
        contrast_image = ImageEnhance.Contrast(brightness_image).enhance(random_factor)  # 調整圖像對比度
        random_factor = np.random.randint(0, 31) / 10.  # 隨機因子
        return ImageEnhance.Sharpness(contrast_image).enhance(random_factor)  # 調整圖像銳度

    @staticmethod
    def randomGaussian(image, mean=0.2, sigma=0.3):
        """
         對圖像進行高斯噪聲處理
        :param image:
        :return:
        """

        def gaussianNoisy(im, mean=0.2, sigma=0.3):
            """
            對圖像做高斯噪音處理
            :param im: 單通道圖像
            :param mean: 偏移量
            :param sigma: 標準差
            :return:
            """
            for _i in range(len(im)):
                im[_i] += random.gauss(mean, sigma)
            return im

        # 將圖像轉化成數組
        img = np.asarray(image)
        img.flags.writeable = True  # 將數組改為讀寫模式
        width, height = img.shape[:2]
        img_r = gaussianNoisy(img[:, :, 0].flatten(), mean, sigma)
        img_g = gaussianNoisy(img[:, :, 1].flatten(), mean, sigma)
        img_b = gaussianNoisy(img[:, :, 2].flatten(), mean, sigma)
        img[:, :, 0] = img_r.reshape([width, height])
        img[:, :, 1] = img_g.reshape([width, height])
        img[:, :, 2] = img_b.reshape([width, height])
        return Image.fromarray(np.uint8(img))

    @staticmethod
    def saveImage(image, path):
        image.save(path)


def makeDir(path):
    try:
        if not os.path.exists(path):
            if not os.path.isfile(path):
                # os.mkdir(path)
                os.makedirs(path)
            return 0
        else:
            return 1
    except Exception, e:
        print str(e)
        return -2


def imageOps(func_name, image, des_path, file_name, times=5):
    funcMap = {"randomRotation": DataAugmentation.randomRotation,
               "randomCrop": DataAugmentation.randomCrop,
               "randomColor": DataAugmentation.randomColor,
               "randomGaussian": DataAugmentation.randomGaussian
               }
    if funcMap.get(func_name) is None:
        logger.error("%s is not exist", func_name)
        return -1

    for _i in range(0, times, 1):
        new_image = funcMap[func_name](image)
        DataAugmentation.saveImage(new_image, os.path.join(des_path, func_name + str(_i) + file_name))


opsList = {"randomRotation", "randomCrop", "randomColor", "randomGaussian"}


def threadOPS(path, new_path):
    """
    多線程處理事務
    :param src_path: 資源文件
    :param des_path: 目的地文件
    :return:
    """
    if os.path.isdir(path):
        img_names = os.listdir(path)
    else:
        img_names = [path]
    for img_name in img_names:
        print img_name
        tmp_img_name = os.path.join(path, img_name)
        if os.path.isdir(tmp_img_name):
            if makeDir(os.path.join(new_path, img_name)) != -1:
                threadOPS(tmp_img_name, os.path.join(new_path, img_name))
            else:
                print 'create new dir failure'
                return -1
                # os.removedirs(tmp_img_name)
        elif tmp_img_name.split('.')[1] != "DS_Store":
            # 讀取文件并進行操作
            image = DataAugmentation.openImage(tmp_img_name)
            threadImage = [0] * 5
            _index = 0
            for ops_name in opsList:
                threadImage[_index] = threading.Thread(target=imageOps,
                                                       args=(ops_name, image, new_path, img_name,))
                threadImage[_index].start()
                _index += 1
                time.sleep(0.2)


if __name__ == '__main__':
    threadOPS("/home/pic-image/train/12306train",
              "/home/pic-image/train/12306train3")

3.2 TensorFlow 實現

#encoding:utf-8
'''
tf 參考鏈接 :https://tensorflow.google.cn/api_guides/python/image
增加數據量,減輕過擬合,增強模型的泛化能力
在預測時也可以使用
'''
import numpy as np
import os
import math
import tensorflow as tf
from skimage import io
import random
import matplotlib.pyplot as plt
 
def read_image(image_path):
    image_raw_data = tf.gfile.FastGFile(image_path,'rb').read()
    image_data = tf.image.decode_png(image_raw_data)
    return image_data
 
'''
    #圖像大小的調整,放大縮小
    不同尺寸
    tf.image.resize_images(img,size,size,method), 0,默認 雙線性插值;1,最近鄰算法;
                                              2, 雙3次插值法;3,面積插值法
'''
def resize_image(image_data):
    
    res = []
    image_biliner = tf.image.resize_images(image_data,[256,256],method=0)   
 
    image_nn = tf.image.resize_images(image_data,[256,256],method=1)
    image_bicubic = tf.image.resize_images(image_data,[256,256],method=2)
    image_area = tf.image.resize_images(image_data,[256,256],method=3)
 
    res.append(tf.to_int32(image_biliner))
    res.append(tf.to_int32(image_nn))
    res.append(tf.to_int32(image_bicubic))
    res.append(tf.to_int32(image_area))
    
    return res
 
'''
#裁剪
識別不同位置的物體
'''
def crop_image(image_data):
    res = []
    #在中間位置進行裁剪或者周圍填充0
    image_crop = tf.image.resize_image_with_crop_or_pad(image_data,256,256) 
    image_pad = tf.image.resize_image_with_crop_or_pad(image_data,512,512)
    
    #按照比列 裁剪圖像的中心區域
    image_center_crop = tf.image.central_crop(image_data,0.5)
 
    #隨機裁剪(常用方法)
    image_random_crop0 = tf.random_crop(image_data,[300,300,3])
    image_random_crop1 = tf.random_crop(image_data,[300,300,3])
 
    res.append(tf.to_int32(image_crop))
    res.append(tf.to_int32(image_pad))
    res.append(tf.to_int32(image_center_crop))
    res.append(tf.to_int32(image_random_crop0)) 
    res.append(tf.to_int32(image_random_crop1))
 
    return res
 
'''
    #旋轉(鏡像)
    圖像旋轉不會影響識別的結果,可以在多個角度進行旋轉,使模型可以識別不同角度的物體
    當旋轉或平移的角度較小時,可以通過maxpooling來保證旋轉和平移的不變性。
'''
def flip_image(image_data):
 
    #鏡像
    res = []
    #上下翻轉
    image_up_down_flip = tf.image.flip_up_down(image_data)
 
    #左右翻轉
    image_left_right_filp = tf.image.flip_left_right(image_data)
 
    #對角線旋轉
    image_transpose = tf.image.transpose_image(image_data)
 
    #旋轉90度 
    image_rot1 = tf.image.rot90(image_data,1)
    image_rot2 = tf.image.rot90(image_data,2)
    image_rot3 = tf.image.rot90(image_data,3)
 
    res.append(tf.to_int32(image_up_down_flip))
    res.append(tf.to_int32(image_left_right_filp))
    res.append(tf.to_int32(image_transpose))
    res.append(tf.to_int32(image_rot1))
    res.append(tf.to_int32(image_rot2))
    res.append(tf.to_int32(image_rot3))
 
    return res
 
#圖像色彩調整
'''
    根據原始數據模擬出更多的不同場景下的圖像
    brightness(亮度),適應不同光照下的物體
    constrast(對比度), hue(色彩), saturation(飽和度) 
    可自定義和隨機
'''
def color_image(image_data):
    res = []
 
    image_random_brightness = tf.image.random_brightness(image_data,0.5)
    image_random_constrast = tf.image.random_contrast(image_data,0,1)
    image_random_hue = tf.image.random_hue(image_data,0.5)
    image_random_saturation = tf.image.random_saturation(image_data,0,1)
 
    #顏色空間變換
    images_data = tf.to_float(image_data)
    image_hsv_rgb = tf.image.rgb_to_hsv(images_data)
    # image_gray_rgb = tf.image.rgb_to_grayscale(image_data)
    # image_gray_rgb = tf.expand_dims(image_data[2],1)
 
    res.append(tf.to_int32(image_random_brightness))
    res.append(tf.to_int32(image_random_constrast))
    res.append(tf.to_int32(image_random_hue))
    res.append(tf.to_int32(image_random_saturation))
    res.append(tf.to_int32(image_hsv_rgb))
    return res
 
#添加噪聲
def PCA_Jittering(img):
 
    img_size = img.size/3
    print(img.size,img_size)
    img1= img.reshape(int(img_size),3)
    img1 = np.transpose(img1)
    img_cov = np.cov([img1[0], img1[1], img1[2]])  
    #計算矩陣特征向量
    lamda, p = np.linalg.eig(img_cov)
 
    p = np.transpose(p)  
    #生成正態分布的隨機數
    alpha1 = random.normalvariate(0,0.2)  
    alpha2 = random.normalvariate(0,0.2)  
    alpha3 = random.normalvariate(0,0.2)  
 
    v = np.transpose((alpha1*lamda[0], alpha2*lamda[1], alpha3*lamda[2])) #加入擾動  
    add_num = np.dot(p,v)  
 
    img2 = np.array([img[:,:,0]+add_num[0], img[:,:,1]+add_num[1], img[:,:,2]+add_num[2]])  
 
    img2 = np.swapaxes(img2,0,2)  
    img2 = np.swapaxes(img2,0,1)  
 
    return img2
 
def main(_):
    image_path = 'dog.png'
    image_data = read_image(image_path)
    img = tf.image.per_image_standardization(image_data)
    resize = resize_image(image_data)
    crop = crop_image(image_data)
    flip = flip_image(image_data)
    color = color_image(image_data)
    init = tf.global_variables_initializer()
 
    with tf.Session() as sess:
        sess.run(init)
        img, resize_res, crop_res, flip_res, color_res = sess.run([img,
            resize,crop,flip,color])
        
        res = []
        res.append(resize_res)
        res.append(crop_res)
        res.append(flip_res)
        res.append(color_res)
 
        for cat in res:
            fig = plt.figure()
            num = 1
            for i in cat:   
                x = math.ceil(len(cat)/2) #向上取整             
                fig.add_subplot(2,x,num)    
                plt.imshow(i)
                num = num+1
            plt.show()
        img = PCA_Jittering(img)
        plt.imshow(img)
        plt.show()
if __name__ == '__main__':
    tf.app.run()

3.3 Keras 實現

#!/usr/bin/env python
#-*- coding: utf-8 -*-

# import packages
from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img

datagen = ImageDataGenerator(
        rotation_range=0.2,
        width_shift_range=0.2,
        height_shift_range=0.2,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True,
        fill_mode='nearest')

img = load_img('~/Desktop/lena.jpg')  # this is a PIL image, please replace to your own file path
x = img_to_array(img)  # this is a Numpy array with shape (3, 150, 150)
x = x.reshape((1,) + x.shape)  # this is a Numpy array with shape (1, 3, 150, 150)

# the .flow() command below generates batches of randomly transformed images
# and saves the results to the `preview/` directory

i = 0
for batch in datagen.flow(x, 
                          batch_size=1,
                          save_to_dir='~/Desktop/preview',  
                          save_prefix='lena', 
                          save_format='jpg'):
    i += 1
    if i > 20:
        break  # otherwise the generator would loop indefinitely

3.4 Pytorch實現

import numpy as np
import torch
from torch import nn
import torch.nn.functional as F
from torch.autograd import Variable
from torchvision.datasets import CIFAR10
from utils import train, resnet
from torchvision import transforms as tfs
# 使用數據增強
def train_tf(x):
    im_aug = tfs.Compose([
        tfs.Resize(120),
        tfs.RandomHorizontalFlip(),
        tfs.RandomCrop(96),
        tfs.ColorJitter(brightness=0.5, contrast=0.5, hue=0.5),
        tfs.ToTensor(),
        tfs.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
    ])
    x = im_aug(x)
    return x

def test_tf(x):
    im_aug = tfs.Compose([
        tfs.Resize(96),
        tfs.ToTensor(),
        tfs.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
    ])
    x = im_aug(x)
    return x

train_set = CIFAR10('./data', train=True, transform=train_tf)
train_data = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True)
test_set = CIFAR10('./data', train=False, transform=test_tf)
test_data = torch.utils.data.DataLoader(test_set, batch_size=128, shuffle=False)

net = resnet(3, 10)
optimizer = torch.optim.SGD(net.parameters(), lr=0.01)
criterion = nn.CrossEntropyLoss()
train(net, train_data, test_data, 10, optimizer, criterion)

3.5 imgaug 圖像增強庫實現

更加去參考:https://github.com/aleju/imgaug

4. 總結

數據增強主要對訓練數據進行操作的一種正則化技術。顧名思義,數據增強通過應用一系列方法隨機地改變訓練數據,比如平移,旋轉,剪切和翻轉等。數據增強的詳細變換幅度需要根據具體的應用數據而設計,只要注意一點:應用這些簡單的轉換不能改變輸入圖像的標簽。每個通過增強得到的圖像都可以被認為是一個“新”圖像。這樣我們可以不斷的給模型提供新的訓練樣本,使模型能夠學習到更加具有辨別力,更具泛化性的特征。

應用數據增強技術可以提高模型的準確率,同時有助于減輕過擬合。此外,數據增強也可以增加數據量,降低深度學習需要的人工標記的大量數據集。盡管收集“自然”的訓練樣本越多越好,但是在無法增加真實的訓練樣本時,數據增強可以用來克服小數據集的局限性。

5. 參考

深度學習與計算機視覺(PB-02)-數據增強

深度學習中的數據增強(data augmentation)

Keras ImageDataGenerator使用

深度學習入門之Pytorch——數據增強

圖像增強庫Image augmentation for machine learning experiments

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370