AlexNet当年能够取得巨大的成功,不仅仅是方法上的革新,图像增强这一步数据处理操作也功不可没。
图像增强通过一系列随即变换即可将一张图片变成多张不重复的图片。虽然图片特征基本还是一样的,但在深度学习领域,数据集多意味着效果又更近一步了。
接下来展示一下如何做图像增强:
一、数据读取
1)使用OpenCV读取一下要增强的图片及相关信息:
src=cv.imread("F:/test/flower.jpg",1)
print(src.shape)
cv.imshow("image",src)
cv.waitKey(0)
结果:
可以看到,图片的大小为227×227,3通道。
2)使用mxnet image读取图像:
img=image.imdecode(open("F:/test/dog.jpg","rb").read())
plt.imshow(img.asnumpy())
plt.show()
结果:
二、水平翻转
下面我们来定义一个方法,该方法主要是用来显示图片:
def apply(img,aug,n=3):
_,figs=plt.subplots(n,n,figsize=(8,8))
for i in range(n):
for j in range(n):
# 通常图片都是int8类型的,图像做变换需要转成float类型
x=img.astype("float32")
# 做完变换之后,像素值可能会大于255或小于0
y=aug(x).clip(0,254)
figs[i][j].imshow(y.asnumpy()/255.0) # 显示浮点数时,像素值要在[0,1]
figs[i][j].axes.get_xaxis().set_visible(False)
figs[i][j].axes.get_yaxis().set_visible(False)
plt.show()
mxnet模块中有一个image方法,该方法实现了一个水平翻转的方法:
# 1、水平翻转
aug=image.HorizontalFlipAug(0.5) # 以50%的概率做水平翻转
apply(img,aug,n=3)
运行结果:
可以看到,小狗的图片有的以50%的概率被水平变换了,虽然水平变换后还是同一张图片,但是这仅仅是人眼的直观感受,对于计算机、卷积神经网络来说,它就是一个全新的图片,所以,水平翻转在做图像增强的时候一定要首选!!!
三、随机裁剪
通常来说,识别的对象并不一定在正中间,所以为了降低CNN对位置信息的敏感度,随机裁剪的方法也很重要,它以随机的方式把对象的特征放在各个地方。(PS:CNN对位置信息很敏感,因为它采用了滑动窗口,max pooling就是能够在解决敏感位置的问题上起到一定作用的方法。)
# 2、随机裁剪
# 对图片做随机裁剪,裁剪出一个150X150的图片
aug1=image.RandomCropAug((150,150))
apply(img,aug1,n=3)
结果:
还有一种裁剪方法,就是把某一块区域裁剪出来,并把该区域resize成227×227,同时保证裁剪该区域的长宽比在某个范围内。
# 3、还有一种裁剪方法,就是把某一块区域裁剪出来,并把该区域resize成227X227,
# 同时保证裁剪该区域的长宽比在某个范围内
aug2=image.RandomSizedCropAug(size=(227,227),area=0.1,ratio=(0.5,2)) # area为至少裁剪的区域,ratio为长宽比
apply(img,aug1,n=3)
结果:
从图片上感觉不到什么变化,其实使用这个方法之后,图片的大小不仅仅变为了227×227,形状其实也进行了变化。
四、亮度变化
aug3=image.BrightnessJitterAug(0.5) # 随机将亮度增加或减小在0-50%间的一个量
apply(img,aug3)
结果:
五、颜色变换
手机或相机拍摄,在不同光照下的颜色也是不同的,颜色变换就是模拟这种情况的发生。
aug4=image.HueJitterAug(0.5) #色调变换
apply(img,aug4)
结果:
实例应用
本章使用ResNet对CIFAR10数据集进行训练:
1、定义数据扩增方案
# 由于应用到数个增强方法,所以写一个函数,可以逐个应用到增强方法
def augs_list(img,augs):
for f in augs:
img=f(img)
return img
# 对于训练图片,使用随机水平翻转和裁剪,CIFAR10图片的大小为32X32X3,这里裁剪成28X28X3
train_augs=[
image.HorizontalFlipAug(0.5),
image.RandomCropAug(size=(28,28))
]
test_augs=[image.RandomCropAug(size=(28,28))]
2、数据读取
ef get_transform(augs):
def transform(data,label):
data=data.astype("float32")
label=label.astype("float32")
if augs is not None:
data=augs_list(img=data,augs=augs)
data=nd.transpose(data,axes=(2,0,1)) # hwc转为chw
return data/255.0,label
return transform
def get_data(batch_size,train_augs,test_augs=None):
cifar10_train=gn.data.vision.CIFAR10(train=True,transform=get_transform(train_augs))
cifar10_test = gn.data.vision.CIFAR10(train=False, transform=get_transform(test_augs))
train_data=gn.data.DataLoader(dataset=cifar10_train,batch_size=batch_size,
shuffle=True)
test_data = gn.data.DataLoader(dataset=cifar10_test, batch_size=batch_size,
shuffle=False)
return train_data,test_data
画出前几张看看:
train_36,test_36=get_data(36,train_augs=train_augs,test_augs=test_augs)
for im, label in train_36:
break
_,figs=plt.subplots(6,6,figsize=(8,8))
for i in range(6):
for j in range(6):
x=nd.transpose(im[i*3+j],(1,2,0))
figs[i][j].imshow(x.asnumpy())
figs[i][j].axes.get_xaxis().set_visible(False)
figs[i][j].axes.get_yaxis().set_visible(False)
plt.show()
结果:
关于残差网络之前有讲过,所以这里放上所有代码:
import mxnet.ndarray as nd
import mxnet.autograd as ag
import mxnet.gluon as gn
import mxnet as mx
import matplotlib.pyplot as plt
from mxnet import init
class Residual(gn.nn.Block):
def __init__(self,channels,same_shape=True,**kwargs):
super(Residual, self).__init__(**kwargs)
'''
:param channels: 输出通道数
:param same_shape: 输入和输出的shape是否要一样,因为可能要做加法
'''
self.same_shape=same_shape
with self.name_scope():
if same_shape:
strides = 1
else:
strides = 2
self.conv1=gn.nn.Conv2D(channels,kernel_size=3,padding=1,strides=strides)
self.bn1=gn.nn.BatchNorm()
self.conv2 = gn.nn.Conv2D(channels, kernel_size=3, padding=1)
self.bn2 = gn.nn.BatchNorm()
if not same_shape: # 如果输出通道数不一致,启用1X1卷积
self.conv3=gn.nn.Conv2D(channels,kernel_size=1,strides=strides)
def forward(self, x):
out=nd.relu(self.bn1(self.conv1(x)))
out=self.bn2(self.conv2(out))
if not self.same_shape: # 如果维度不一样
x=self.conv3(x) # 这样out和x的维度就一样了
return nd.relu(out+x)
# 运行一个实例看看
# blk=Residual(channels=16,same_shape=False)
# blk.initialize()
# x=nd.random_normal(shape=(1,3,8,8)) # NCHW
# print(blk(x).shape)
ctx=mx.gpu()
'''---ResNet模型定义---'''
def ResNet():
# 随便写一个残差,依旧分为6块
# block 1
b1=gn.nn.Sequential()
b1.add(gn.nn.Conv2D(channels=32,kernel_size=5,strides=2))
# block 2
b2=gn.nn.Sequential()
b2.add(Residual(channels=64,same_shape=False),# 因为上一是16,所以维度不一样
Residual(channels=64))
# block 3
b3 = gn.nn.Sequential()
b3.add(Residual(channels=128, same_shape=False),
Residual(channels=128))
# block 4
b4 = gn.nn.Sequential()
b4.add(Residual(channels=256, same_shape=False),
Residual(channels=256))
# block 5
b5 = gn.nn.Sequential()
b5.add(Residual(channels=512, same_shape=False),
Residual(channels=512))
# block 6
b6 = gn.nn.Sequential()
b6.add(gn.nn.GlobalAvgPool2D(),
gn.nn.Dense(10)) # 10个分类
# 结合所有block
net=gn.nn.Sequential()
net.add(b1,b2,b3,b4,b5,b6)
return net
net=ResNet()
net.initialize(init=init.Xavier(),ctx=ctx)
# X=nd.random_normal(shape=(1,3,32,32))
# for layer in net:
# X = layer(X)
# print(layer.name, 'output shape:\t', X.shape)
'''---读取数据和预处理---'''
def load_data_fashion_mnist(batch_size, resize=None):
transformer = []
if resize:
transformer += [gn.data.vision.transforms.Resize(resize)]
transformer += [gn.data.vision.transforms.ToTensor()]
transformer = gn.data.vision.transforms.Compose(transformer)
mnist_train = gn.data.vision.FashionMNIST(train=True)
mnist_test = gn.data.vision.FashionMNIST(train=False)
train_iter = gn.data.DataLoader(
mnist_train.transform_first(transformer), batch_size, shuffle=True)
test_iter = gn.data.DataLoader(
mnist_test.transform_first(transformer), batch_size, shuffle=False)
return train_iter, test_iter
batch_size=128
train_iter,test_iter=load_data_fashion_mnist(batch_size,resize=96) # 96,因为图片加大的话训练很慢,而且显存会吃不消
# 定义准确率
def accuracy(output,label):
return nd.mean(output.argmax(axis=1)==label).asscalar()
def evaluate_accuracy(data_iter,net):# 定义测试集准确率
acc=0
for data,label in data_iter:
data, label = data.as_in_context(ctx), label.as_in_context(ctx)
label = label.astype('float32')
output=net(data)
acc+=accuracy(output,label)
return acc/len(data_iter)
# softmax和交叉熵分开的话数值可能会不稳定
cross_loss=gn.loss.SoftmaxCrossEntropyLoss()
# 优化
train_step=gn.Trainer(net.collect_params(),'sgd',{"learning_rate":0.2}) #因为使用了BN,所以学习率可以大一些
# 训练
lr=0.1
epochs=20
for epoch in range(epochs):
n=0
train_loss=0
train_acc=0
for image,y in train_iter:
image, y = image.as_in_context(ctx), y.as_in_context(ctx)
y = y.astype('float32')
with ag.record():
output = net(image)
loss = cross_loss(output, y)
loss.backward()
train_step.step(batch_size)
train_loss += nd.mean(loss).asscalar()
train_acc += accuracy(output, y)
test_acc = evaluate_accuracy(test_iter, net)
print("Epoch %d, Loss:%f, Train acc:%f, Test acc:%f"
%(epoch,train_loss/len(train_iter),train_acc/len(train_iter),test_acc))
训练结果: