1. 數(shù)據(jù)增強Data Augmentation
- 數(shù)據(jù)增強讓有限的數(shù)據(jù)產(chǎn)生更多的數(shù)據(jù),增加訓(xùn)練樣本的數(shù)量以及多樣性(噪聲數(shù)據(jù)),提升模型魯棒性。神經(jīng)網(wǎng)絡(luò)需要大量的參數(shù),許許多多的神經(jīng)網(wǎng)路的參數(shù)都是數(shù)以百萬計,而使得這些參數(shù)可以正確工作則需要大量的數(shù)據(jù)進(jìn)行訓(xùn)練,但在很多實際的項目中,我們難以找到充足的數(shù)據(jù)來完成任務(wù)。
- 隨機改變訓(xùn)練樣本可以降低模型對某些屬性的依賴,從而提高模型的泛化能力。
- 例如,我們可以對圖像進(jìn)行不同方式的裁剪,讓物體以不同的實例出現(xiàn)在圖像的不同位置,這同樣能夠降低模型對目標(biāo)位置的敏感性。
- 例如,我們也可以調(diào)整亮度、對比度、飽和度和色調(diào) 等因素來降低模型對 色彩的敏感度。
2. 數(shù)據(jù)增強的分類
數(shù)據(jù)增強可以分為兩類,一類是離線增強,一類是在線增強。
離線增強 : 直接對數(shù)據(jù)集進(jìn)行處理,數(shù)據(jù)的數(shù)目會變成增強因子乘以原數(shù)據(jù)集的數(shù)目,這種方法常常用于數(shù)據(jù)集很小的時候。
在線增強 : 這種增強的方法用于,獲得 batch 數(shù)據(jù)之后,然后對這個 batch 的數(shù)據(jù)進(jìn)行增強,如旋轉(zhuǎn)、平移、翻折等相應(yīng)的變化,由于有些數(shù)據(jù)集不能接受線性級別的增長,這種方法長用于大的數(shù)據(jù)集,很多機器學(xué)習(xí)框架已經(jīng)支持了這種數(shù)據(jù)增強方式,并且可以使用 GPU 優(yōu)化計算。
3. 數(shù)據(jù)增強實現(xiàn)
數(shù)據(jù)增強一般是圖像用的多,都是一些常用的方法,比如random crop,隨機反轉(zhuǎn),隨機對比度增強,顏色變化等等,一般來講隨機反轉(zhuǎn)和一個小比例的random resize,再接random crop比較常用。NLP中將字和詞連接起來就形成了一個新樣本,也屬于數(shù)據(jù)增強。
圖片數(shù)據(jù)增強通常只是針對訓(xùn)練數(shù)據(jù),對于測試數(shù)據(jù)則用得較少。
3.1 PIL 實現(xiàn)
# -*- coding:utf-8 -*-
"""數(shù)據(jù)增強
1. 翻轉(zhuǎn)變換 flip
2. 隨機修剪 random crop
3. 色彩抖動 color jittering
4. 平移變換 shift
5. 尺度變換 scale
6. 對比度變換 contrast
7. 噪聲擾動 noise
8. 旋轉(zhuǎn)變換/反射變換 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:
"""
包含數(shù)據(jù)增強的八種方式
"""
def __init__(self):
pass
@staticmethod
def openImage(image):
return Image.open(image, mode="r")
@staticmethod
def randomRotation(image, mode=Image.BICUBIC):
"""
對圖像進(jìn)行隨機任意角度(0~360度)旋轉(zhuǎn)
:param mode 鄰近插值,雙線性插值,雙三次B樣條插值(default)
:param image PIL的圖像image
:return: 旋轉(zhuǎn)轉(zhuǎn)之后的圖像
"""
random_angle = np.random.randint(1, 360)
return image.rotate(random_angle, mode)
@staticmethod
def randomCrop(image):
"""
對圖像隨意剪切,考慮到圖像大小范圍(68,68),使用一個一個大于(36*36)的窗口進(jìn)行截圖
: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):
"""
對圖像進(jìn)行顏色抖動
:param image: PIL的圖像image
:return: 有顏色色差的圖像image
"""
random_factor = np.random.randint(0, 31) / 10. # 隨機因子
color_image = ImageEnhance.Color(image).enhance(random_factor) # 調(diào)整圖像的飽和度
random_factor = np.random.randint(10, 21) / 10. # 隨機因子
brightness_image = ImageEnhance.Brightness(color_image).enhance(random_factor) # 調(diào)整圖像的亮度
random_factor = np.random.randint(10, 21) / 10. # 隨機因1子
contrast_image = ImageEnhance.Contrast(brightness_image).enhance(random_factor) # 調(diào)整圖像對比度
random_factor = np.random.randint(0, 31) / 10. # 隨機因子
return ImageEnhance.Sharpness(contrast_image).enhance(random_factor) # 調(diào)整圖像銳度
@staticmethod
def randomGaussian(image, mean=0.2, sigma=0.3):
"""
對圖像進(jìn)行高斯噪聲處理
:param image:
:return:
"""
def gaussianNoisy(im, mean=0.2, sigma=0.3):
"""
對圖像做高斯噪音處理
:param im: 單通道圖像
:param mean: 偏移量
:param sigma: 標(biāo)準(zhǔn)差
:return:
"""
for _i in range(len(im)):
im[_i] += random.gauss(mean, sigma)
return im
# 將圖像轉(zhuǎn)化成數(shù)組
img = np.asarray(image)
img.flags.writeable = True # 將數(shù)組改為讀寫模式
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):
"""
多線程處理事務(wù)
: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":
# 讀取文件并進(jìn)行操作
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 實現(xiàn)
#encoding:utf-8
'''
tf 參考鏈接 :https://tensorflow.google.cn/api_guides/python/image
增加數(shù)據(jù)量,減輕過擬合,增強模型的泛化能力
在預(yù)測時也可以使用
'''
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
'''
#圖像大小的調(diào)整,放大縮小
不同尺寸
tf.image.resize_images(img,size,size,method), 0,默認(rèn) 雙線性插值;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 = []
#在中間位置進(jìn)行裁剪或者周圍填充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)
#按照比列 裁剪圖像的中心區(qū)域
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
'''
#旋轉(zhuǎn)(鏡像)
圖像旋轉(zhuǎn)不會影響識別的結(jié)果,可以在多個角度進(jìn)行旋轉(zhuǎn),使模型可以識別不同角度的物體
當(dāng)旋轉(zhuǎn)或平移的角度較小時,可以通過maxpooling來保證旋轉(zhuǎn)和平移的不變性。
'''
def flip_image(image_data):
#鏡像
res = []
#上下翻轉(zhuǎn)
image_up_down_flip = tf.image.flip_up_down(image_data)
#左右翻轉(zhuǎn)
image_left_right_filp = tf.image.flip_left_right(image_data)
#對角線旋轉(zhuǎn)
image_transpose = tf.image.transpose_image(image_data)
#旋轉(zhuǎn)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
#圖像色彩調(diào)整
'''
根據(jù)原始數(shù)據(jù)模擬出更多的不同場景下的圖像
brightness(亮度),適應(yīng)不同光照下的物體
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)
#生成正態(tài)分布的隨機數(shù)
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 實現(xiàn)
#!/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實現(xiàn)
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
# 使用數(shù)據(jù)增強
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 圖像增強庫實現(xiàn)
更加去參考:https://github.com/aleju/imgaug
4. 總結(jié)
數(shù)據(jù)增強主要對訓(xùn)練數(shù)據(jù)進(jìn)行操作的一種正則化技術(shù)。顧名思義,數(shù)據(jù)增強通過應(yīng)用一系列方法隨機地改變訓(xùn)練數(shù)據(jù),比如平移,旋轉(zhuǎn),剪切和翻轉(zhuǎn)等。數(shù)據(jù)增強的詳細(xì)變換幅度需要根據(jù)具體的應(yīng)用數(shù)據(jù)而設(shè)計,只要注意一點:應(yīng)用這些簡單的轉(zhuǎn)換不能改變輸入圖像的標(biāo)簽。每個通過增強得到的圖像都可以被認(rèn)為是一個“新”圖像。這樣我們可以不斷的給模型提供新的訓(xùn)練樣本,使模型能夠?qū)W習(xí)到更加具有辨別力,更具泛化性的特征。
應(yīng)用數(shù)據(jù)增強技術(shù)可以提高模型的準(zhǔn)確率,同時有助于減輕過擬合。此外,數(shù)據(jù)增強也可以增加數(shù)據(jù)量,降低深度學(xué)習(xí)需要的人工標(biāo)記的大量數(shù)據(jù)集。盡管收集“自然”的訓(xùn)練樣本越多越好,但是在無法增加真實的訓(xùn)練樣本時,數(shù)據(jù)增強可以用來克服小數(shù)據(jù)集的局限性。
5. 參考
深度學(xué)習(xí)與計算機視覺(PB-02)-數(shù)據(jù)增強
深度學(xué)習(xí)中的數(shù)據(jù)增強(data augmentation)