找回密码
 立即注册
搜索

深度学习:从零末尾构造一个辨认猫狗图片的卷积网络

更多内容请点击链接!

在深度学习的项目实际中,往往会遇到两个非常难以克制的难题,一是算力,要得到准确的结果,你需求设计几千层,规模庞大的神经网络,然后运用几千个GPU,把神经网络布置到这些GPU上停止运算;第二个难以克制的困难就是数据量,要想得到足够准确的结果,必须依赖于足够量的数据来训练网络模型。本节我们先看看第二个成绩如何处理。

我们将开放一个神经网络,用于辨认猫狗照片,用于训练模型的照片数量不多,大概4000张左右,猫狗各有2000张,我们将用2000张图片训练模型,1000张用来校验模型,最后1000张对模型停止测试。基于这些有限的数据,我们从零末尾构造一个卷积网络模型,在没有运用任何优化手腕的状况下,先使得模型的辨认准确率达到70%左右,这时假如继续加大模型的训练强度会惹起过度拟合,此时我们引入数据扩展法,一种能有效应对视觉辨认过程中出现过度拟合的技巧,运用该方法我们可以把网络的准确度提升到80%左右,接着我们再运用其他方法,例如特征抽取,模型预训练,再加上一些具有参数调优,最后让模型的准确率达到97%。

首先我们的训练数据来自于kaggle网站,我曾经下载并上传到下面链接的对应课程页面里:

更详细的讲解和代码调试演示过程,请点击链接

把图片下载到本地解压后,我们再运用下面代码,将相关图片拷贝到不同的途径下:

import os, shutil

#数据包被解压的途径

original_dataset_dir = '/Users/chenyi/Documents/人工智能/all/train'

#构造一个专门用于存储图片的途径

base_dir = '/Users/chenyi/Documents/人工智能/all/cats_and_dogs_small'

os.makedirs(base_dir, exist_ok=True)

#构造途径存储训练数据,校验数据以及测试数据

train_dir = os.path.join(base_dir, 'train')

os.makedirs(train_dir, exist_ok = True)

test_dir = os.path.join(base_dir, 'test')

os.makedirs(test_dir, exist_ok = True)

validation_dir = os.path.join(base_dir, 'validation')

os.makedirs(validation_dir, exist_ok = True)

#构造专门存储猫图片的途径,用于训练网络

train_cats_dir = os.path.join(train_dir, 'cats')

os.makedirs(train_cats_dir, exist_ok = True)

#构造存储狗图片途径,用于训练网络

train_dogs_dir = os.path.join(train_dir, 'dogs')

os.makedirs(train_dogs_dir, exist_ok = True)

#构造存储猫图片的途径,用于校验网络

validation_cats_dir = os.path.join(validation_dir, 'cats')

os.makedirs(validation_cats_dir, exist_ok = True)

#构造存储狗图片的途径,用于校验网络

validation_dogs_dir = os.path.join(validation_dir, 'dogs')

os.makedirs(validation_dogs_dir, exist_ok = True)

#构造存储猫图片途径,用于测试网络

test_cats_dir = os.path.join(test_dir, 'cats')

os.makedirs(test_cats_dir, exist_ok = True)

#构造存储狗图片途径,用于测试网络

test_dogs_dir = os.path.join(test_dir, 'dogs')

os.makedirs(test_dogs_dir, exist_ok = True)

#把前1000张猫图片复制到训练途径

fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]

for fname in fnames:

src = os.path.join(original_dataset_dir, fname)

dst = os.path.join(train_cats_dir, fname)

shutil.copyfile(src, dst)

#把接着的500张猫图片复制到校验途径

fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]

for fname in fnames:

src = os.path.join(original_dataset_dir, fname)

dst = os.path.join(validation_cats_dir, fname)

shutil.copyfile(src, dst)

#把接着的500张猫图片复制到测试途径

fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]

for fname in fnames:

src = os.path.join(original_dataset_dir, fname)

dst = os.path.join(test_cats_dir, fname)

shutil.copyfile(src, dst)

#把1000张狗图片复制到训练途径

fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]

for fname in fnames:

src = os.path.join(original_dataset_dir, fname)

dst = os.path.join(train_dogs_dir, fname)

shutil.copyfile(src, dst)

#把接下500张狗图片复制到校验途径

fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]

for fname in fnames:

src = os.path.join(original_dataset_dir, fname)

dst = os.path.join(validation_dogs_dir, fname)

shutil.copyfile(src, dst)

#把接上去500张狗图片复制到测试途径

fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]

for fname in fnames:

src = os.path.join(original_dataset_dir, fname)

dst = os.path.join(test_dogs_dir, fname)

shutil.copyfile(src, dst)

print('total trainning cat images: ', len(os.listdir(train_cats_dir)))

print('total training dog images', len(os.listdir(train_dogs_dir)))

print('total validation cat images', len(os.listdir(validation_cats_dir)))

print('total validation dogs images', len(os.listdir(validation_dogs_dir)))

print('total test cat images:', len(os.listdir(test_cats_dir)))

print('total test dog images:', len(os.listdir(test_dogs_dir)))

下面代码把图片分别放置到不同文件夹下,训练用的图片在一个文件夹,校验用的图片在一个文件夹,最后测试用的图片在一个文件夹,下面代码运转后,结果如下:

我们将向上一节例子那样,构造一个Conv2D和MaxPooling2D互相交替的卷积网络。由于我们如今读取的图片比上一节的手写数字图片要到,而且图片的颜色深度比上一节的灰度图要大,因此我们这次构造的网络规模也要相应变大。卷积网络模型的构建代码如下:

from keras import layers

from keras import models

from keras import optimizers

model = models.Sequential()

#输入图片大小是150*150 3表示图片像素用(R,G,B)表示

model.add(layers.Conv2D(32, (3,3), activation='relu', input_shape=(150 , 150, 3)))

model.add(layers.MaxPooling2D((2,2)))

model.add(layers.Conv2D(64, (3,3), activation='relu'))

model.add(layers.MaxPooling2D((2,2)))

model.add(layers.Conv2D(128, (3,3), activation='relu'))

model.add(layers.MaxPooling2D((2,2)))

model.add(layers.Conv2D(128, (3,3), activation='relu'))

model.add(layers.MaxPooling2D((2,2)))

model.add(layers.Flatten())

model.add(layers.Dense(512, activation='relu'))

model.add(layers.Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4),

metrics=['acc'])

model.summary()

下面代码运转后结果如下:

我们看到网络在第六层时,曾经有了三百万个参数!这是由于我们反复做卷积,对输入的矩阵做切片形成的。由于网络需求对数据停止二分,所以最后一层只要一个神经元。

接上去我们看看数据预处理,由于机器学习需求读取大量数据,因此keras框架提供了一些辅助机制,让我们能疾速将数据以批量的方式读入内存,我们看相应代码:

from keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator(rescale = 1./ 255) #把像素点的值除以255,使之在0到1之间

test_datagen = ImageDataGenerator(rescale = 1. / 255)

#generator 实践上是将数据批量读入内存,使得代码能以for in 的方式去方便的访问

train_generator = train_datagen.flow_from_directory(train_dir, target_size=(150, 150),

batch_size=20,class_mode = 'binary')

validation_generator = test_datagen.flow_from_directory(validation_dir,

target_size = (150, 150),

batch_size = 20,

class_mode = 'binary')

#calss_mode 让每张读入的图片对应一个标签值,我们下面一下子读入20张图片,因此还附带着一个数组(20, )

#标签数组的详细值没有设定,由我们后面去运用

for data_batch, labels_batch in train_generator:

print('data batch shape: ', data_batch.shape)

print('labels batch shape: ', labels_batch.shape)

break

Generator 是一种数据批量读取类,而且他们是可循环的,也就是可以对它们运用for in ,在下面我们构造了两个Generator用于读取训练图片和校验图片,同时把图片大小设置为150*150,同时它还能让我们在图片后面附带一个标签值,这就是参数class_mode的作用,由于我们只要猫狗两种图片,因此该标签值不是0就是1,由于train_dir途径下只要两个文件夹,它会为从这两个文件夹中读取的图片分别赋值0和1。

在用for in 遍历generator时,每次遍历都能读取20张图片,而且这个过程是无止境的,当一切图片读取完后,generator又会重头再次读入图片,因此我们必须本人运用break把循环中缀掉。下面代码运转后结果如下:

我们看看如何经过generator把数据高效的传递给网络,代码如下:

history = model.fit_generator(train_generator, steps_per_epoch = 100,

epochs = 30, validation_data = validation_generator,

validation_steps = 50)

网络模型支持直接将generator作为参数输入,由于我们构造的generator一次批量读入20张图片,总共有2000张图片,所以我们将参数steps_per_epoch = 100,这样每次训练时,模型会用for…in… 在train_generator上循环100次,将一切2000张图片全部读取,后面设置校验数据参数时,逻辑也相似,我们指定循环训练模型30次,下面代码执行后,在普通单CPU机器上运转将会非常缓慢,在我的电脑上,大概执行了十几分钟。

训练结束后,我们模型的训练准确率和校验准确率绘制出来,看看模型对数据的处理状况,代码如下:

model.save('cats_and_dogs_small_1.h5')

import matplotlib.pyplot as plt

acc = history.history['acc']

val_acc = history.history['val_acc']

loss = history.history['loss']

val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

#绘制模型对训练数据和校验数据判别的准确率

plt.plot(epochs, acc, 'bo', label = 'trainning acc')

plt.plot(epochs, val_acc, 'b', label = 'validation acc')

plt.title('Trainning and validation accuary')

plt.legend()

plt.show()

plt.figure()

#绘制模型对训练数据和校验数据判别的错误率

plt.plot(epochs, loss, 'bo', label = 'Trainning loss')

plt.plot(epochs, val_loss, 'b', label = 'Validation loss')

plt.title('Trainning and validation loss')

plt.legend()

plt.show()

下面代码运转后,绘制的图形如下:

从第一个图可以看出,模型对训练数据的辨认率不断提升,但是对校验数据的辨认率基本停滞在一个程度,从第二个图看出,模型对训练数据辨认的错误率极具下降,但对校验数据的辨认错误率反而疾速上升了,这表明模型出现了过度拟合的现象,这是在任何机器学习项目中都会遇到的成绩。

在计算机视觉辨认中,有一种技巧叫数据扩展,专门用于图像辨认过程中出现的过度拟合现象。过度拟合出现的一个缘由在于数据量太小,我们遇到的状况正是如此。数据扩展本质上是经过应用现有数据创造出新数据,从而添加数据量,我们经过对原有图片随机停止一些修正,在不改变图片本质的状况下,将一张图片修正成一张新的图片,当然这种修正不能将一只猫改成一只狗,只能将一只黑猫变成一只灰猫,我们要保证在训练中,模型不用多次运算同一张图片,在keras框架内,数据扩展很容易完成,例如下面代码:

datagen = ImageDataGenerator(rotation_range = 40, width_shift_range = 0.2, height_shift_range = 0.2,

shear_range = 0.2, zoom_range = 0.2, horizontal_flip = True, fill_mode = 'nearest')

rotation_range表示对图片停止旋转变化, width_shift 和 height_shift对图片的宽和高停止拉伸,shear_range指定裁剪变化的程度,zoom_range是对图片停止放大减少,horizaontal_flip将图片在程度方向上翻转,fill_mode表示当图片停止变换后产生多余空间时,如何去填充。我们看看把下面变化用到一张详细图片上是什么状况:

from keras.preprocessing import image

fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]

#选择一张猫的照片

img_path = fnames[3]

#加载图片并把它设置为150*150

img = image.load_img(img_path, target_size=(150, 150))

x = image.img_to_array(img)

x = x.reshape((1, ) + x.shape)

i = 0

'''

下面的flow函数能自动帮我们停止指定的各种图形变换,例如拉伸,缩放,裁剪等,它是一个死循环,我们需求本人经过break命令才能跳出来

'''

f, ax = plt.subplots(1,4)

for batch in datagen.flow(x, batch_size = 1):

imgplot = ax.imshow(image.array_to_img(batch[0]))

ax.axis('off')

i += 1

if i % 4 == 0:

break

plt.show()

我们拿出一张猫的图片,然后运用下面代码对图片停止各种变换,下面代码运转后得到结果如下:

从下面可以看到,我们从一张图片就可以经过变换生成好几张不同图片,我们在把图片传入网络前,先经过下面办法扩张图片,那么网络一下子就能获得成倍增长的训练数据,但是这种做法使得重生成的图片与原有图片存在很强的关联性,因此它对改善过度拟合的作用比较有限,因此我们还得运用后面说过的对网络层输入结果随机清零的办法,我们把这两方法结合起来重新训练网络,代码如下:

model = models.Sequential()

...

model.add(layers.Flatten())

#把下层网络的输入结果中的一半数据随机清零

model.add(layers.Dropout(0.5))

...

train_datagen = ImageDataGenerator(rescale = 1./ 255, rotation_range=40,

width_shift_range = 0.2,

height_shift_range = 0.2,

shear_range = 0.2,

zoom_range = 0.2,

horizontal_flip = True

) #把像素点的值除以255,使之在0到1之间,添加图片变换

test_datagen = ImageDataGenerator(rescale = 1. / 255)

#generator 实践上是将数据批量读入内存,使得代码能以for in 的方式去方便的访问

train_generator = train_datagen.flow_from_directory(train_dir, target_size=(150, 150),

batch_size=20,class_mode = 'binary')

validation_generator = test_datagen.flow_from_directory(validation_dir,

target_size = (150, 150),

batch_size = 20,

class_mode = 'binary')

history = model.fit_generator(train_generator, steps_per_epoch = 100,

epochs = 30, validation_data = validation_generator,

validation_steps = 50)

model.save('cats_and_dogs_small_2.h5')

完了,我们再次运转后面的绘图代码,看看网络对训练数据和校验数据的辨认度有何变化,运转后得到的绘制结果如下:

经过数据扩展和输入结果随机清零后,对过度拟合的处理非常有效,从上图看,网络对训练数据和校验数据的辨认正确率最终完全分歧,对两类数据的辨认错误率都在有序下降。此时我们达到了75%的准确率。假如进一步运用数据正轨化以及参数调优等手腕,网络的辨认率还能进一步提升,但是就如车没油跑不远一样,假如数据不足,无论我们运用什么深度去优化,辨认率都很难再有分明的提升,进一步提升辨认率的方法,我们将在下一节详细阐述。

更多内容请先点击下面链接

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

大神点评3

__末世 2019-8-17 08:23:23 显示全部楼层
顶起顶起顶起
回复

使用道具 举报

zey77433 2019-8-18 10:07:38 来自手机 显示全部楼层
众里寻他千百度,蓦然回首在这里!
回复

使用道具 举报

hifi007 2019-8-19 11:55:52 显示全部楼层
大佬,这是大佬
回复

使用道具 举报

高级模式
B Color Image Link Quote Code Smilies