Python-猫狗数据集两阶段分类 原始数据直接训练;数据增强后训练

本博客运行环境为Jupyter Notebook-Python3.7。
由于我使用的是Anaconda3配置的jupyter环境,我也将直接在anaconda下搭建keras环境。

由于我电脑性能不是很好,又是AMD显卡的,本博客是烧cpu版本的,运行过程会比较慢。

下载tensorflow、keras

直接找到Anaconda Powershell右键管理员身份运行。
在这里插入图片描述
输入下列两条命令

conda install tensorflow
conda install keras

安装好后,可以进入jupyter来试一试了。
可以查看keras版本。

import keras
keras.__version__

输出如下:

Using TensorFlow backend.

'2.3.1'

下载数据集并重新划分

可以从这里下载猫狗数据集:https://www.kaggle.com/c/dogs-vs-cats/data
原始数据集包含25000张狗和猫的图片(每个类12500张),543MB。下载并解压后,我们将创建一个包含三个子集的新数据集:1000个样本的训练集(train),500个样本的验证集(validation),500个样本的测试集(test)。

代码如下:

D://jupyterwork//AI//data//kaggle_original_data是存放原始数据集的位置,kaggle_original_data是原始数据集的名字。
D://jupyterwork//AI//data//cats_and_dogs_small是创建的新数据集的位置,cats_and_dogs_small是新数据集的名字。

import os, shutil
# 原始数据集路径
original_dataset_dir = 'D://jupyterwork//AI//data//kaggle_original_data'

# 新的数据集
base_dir = 'D://jupyterwork//AI//data//cats_and_dogs_small'
os.mkdir(base_dir)

# 训练图像、验证图像、测试图像的目录
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)

# 训练集-猫的图片
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)

#训练集-狗的图片
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)

# 验证集-猫的图片
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)

# Directory with our validation dog pictures
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)

# 测试集-猫的图片
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)

# Directory with our validation dog pictures
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)

# 复制1000张猫的图片到train_cats_dir
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张猫的图片到validation_cats_dir
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)
    
# Copy next 500 cat images to test_cats_dir
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)
    
# Copy first 1000 dog images to train_dogs_dir
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)
    
# Copy next 500 dog images to validation_dogs_dir
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)
    
# Copy next 500 dog images to test_dogs_dir
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 training cat images:', len(os.listdir(train_cats_dir)))

total training cat images: 1000

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

total training dog images: 1000

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

total validation cat images: 500

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

total validation dog images: 500

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

total test cat images: 500

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

total test dog images: 500

我们训练集中确实有2000个图像,验证集中有1000个验证图像,测试集中1000个图像。

卷积网络(ConvNet)将是一组交替的Conv2D(具有relu激活)和MaxPooling2D层。从大小150x150的输入开始,最终得到尺寸为7x7的Flatten层之前的特征图。

注意特征图的深度在网络中逐渐增加(从32到128),而特征图的大小正在减少(从148x148到7x7)。这是一个几乎在所有的卷积网络(ConvNet)结构中会看到的模式。

由于我们正在处理二元分类问题,所以我们用一个神经元(一个大小为1的密集层(Dense))和一个sigmoid激活函数来结束网络。该神经元将会被用来查看图像归属于那一类或另一类的概率。

from keras import layers
from keras import models

model = models.Sequential()
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.summary()

运行结果如下:
Model: “sequential_1”
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_1 (Conv2D) (None, 148, 148, 32) 896
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 74, 74, 32) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 72, 72, 64) 18496
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 36, 36, 64) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 34, 34, 128) 73856
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 17, 17, 128) 0
_________________________________________________________________
conv2d_4 (Conv2D) (None, 15, 15, 128) 147584
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 7, 7, 128) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 6272) 0
_________________________________________________________________
dense_1 (Dense) (None, 512) 3211776
_________________________________________________________________
dense_2 (Dense) (None, 1) 513
=================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0
_________________________________________________________________

使用“RMSprop”优化器:

from keras import optimizers

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

数据预处理

1、读入图片文件。
2、将JPEG内容解码为RBG像素网格。
3、把它们转换成浮点张量。
4、将像素值(介于0和255之间)重新缩放到[0,1]间隔。

Keras有一个带有图像处理辅助工具的模块,位于keras.preprocessing.image文件。它包含ImageDataGenerator类,该类可以快速设置Python生成器,该生成器可以自动将磁盘上的图像文件转换成批预处理的张量。

代码如下:

from keras.preprocessing.image import ImageDataGenerator

# 所有的图像将重新进行归一化处理 Rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
# 直接从目录读取图像数据
train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # 所有图像大小会被转换成150x150
        target_size=(150, 150),
        batch_size=20,
        # 由于这是一个二元分类问题,y的label值也会被转换成二元的标签
        class_mode='binary')
# 直接从目录读取图像数据
validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

运行结果如下:
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.

可以看一下其中一个生成器的输出:它生成150x150的RGB图像(shape(20,150,150,3))和二进制标签(shape(20,))。
20是每批样品的数量(批量大小)。ps:生成器无限期地生成这些批:它只是在目标文件夹中的图像上无休止地循环。因此,我们需要在某个点上中断迭代循环。

for data_batch, labels_batch in train_generator:
    print('data batch shape:', data_batch.shape)
    print('labels batch shape:', labels_batch.shape)
    break

运行结果如下:
data batch shape: (20, 150, 150, 3)
labels batch shape: (20,)

训练

我们使用生成器将模型与数据拟合。我们使用fit_generator来实现,它希望第一个参数是Python生成器,将无限期地生成一批输入和目标。由于数据是无休止地生成的,生成器需要知道在声明一个纪元结束之前要从生成器中抽取多少样本。这是steps_per_epoch参数的作用:从生成器中绘制steps_per_epoch批后,即在运行steps_per_epoch梯度下降步骤后,拟合过程将转到下一个epoch。也就是说我们将训练数据的总数除以批量大小的结果作为steps_per_epoch的值。一旦Keras到达这一步,它就会知道这是一个新的epoch。
在我们的例子中,批次是20个大样本,因此它将需要100个批次,直到我们看到2000个样本的目标。

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=50)

运行结果如下:
电脑性能不太好就会跑的有点慢,快一些的每个epoch大概8s左右。

Epoch 1/30
100/100 [==============================] - 54s 543ms/step - loss: 0.6935 - acc: 0.5130 - val_loss: 0.7036 - val_acc: 0.5180
Epoch 2/30
100/100 [==============================] - 56s 559ms/step - loss: 0.6550 - acc: 0.6130 - val_loss: 0.6227 - val_acc: 0.6480
Epoch 3/30
100/100 [==============================] - 54s 545ms/step - loss: 0.6043 - acc: 0.6585 - val_loss: 0.6725 - val_acc: 0.6790
Epoch 4/30
100/100 [==============================] - 54s 542ms/step - loss: 0.5693 - acc: 0.6865 - val_loss: 0.6075 - val_acc: 0.6780
Epoch 5/30
100/100 [==============================] - 54s 536ms/step - loss: 0.5359 - acc: 0.7245 - val_loss: 0.6992 - val_acc: 0.6910
Epoch 6/30
100/100 [==============================] - 54s 542ms/step - loss: 0.5029 - acc: 0.7530 - val_loss: 0.4156 - val_acc: 0.6990
Epoch 7/30
100/100 [==============================] - 58s 575ms/step - loss: 0.4844 - acc: 0.7720 - val_loss: 0.5446 - val_acc: 0.6980
Epoch 8/30
100/100 [==============================] - 55s 545ms/step - loss: 0.4447 - acc: 0.7970 - val_loss: 0.4741 - val_acc: 0.6970
Epoch 9/30
100/100 [==============================] - 54s 542ms/step - loss: 0.4208 - acc: 0.8040 - val_loss: 0.4067 - val_acc: 0.7030
Epoch 10/30
100/100 [==============================] - 54s 541ms/step - loss: 0.3892 - acc: 0.8225 - val_loss: 0.6877 - val_acc: 0.7060
Epoch 11/30
100/100 [==============================] - 55s 551ms/step - loss: 0.3695 - acc: 0.8360 - val_loss: 0.3179 - val_acc: 0.7340
Epoch 12/30
100/100 [==============================] - 55s 547ms/step - loss: 0.3402 - acc: 0.8480 - val_loss: 0.4159 - val_acc: 0.7310
Epoch 13/30
100/100 [==============================] - 56s 559ms/step - loss: 0.3143 - acc: 0.8645 - val_loss: 0.8054 - val_acc: 0.7050
Epoch 14/30
100/100 [==============================] - 57s 567ms/step - loss: 0.2979 - acc: 0.8760 - val_loss: 0.4196 - val_acc: 0.7290
Epoch 15/30
100/100 [==============================] - 54s 545ms/step - loss: 0.2663 - acc: 0.8980 - val_loss: 0.7709 - val_acc: 0.7280
Epoch 16/30
100/100 [==============================] - 54s 545ms/step - loss: 0.2527 - acc: 0.8975 - val_loss: 0.6916 - val_acc: 0.7360
Epoch 17/30
100/100 [==============================] - 55s 550ms/step - loss: 0.2254 - acc: 0.9180 - val_loss: 0.6579 - val_acc: 0.7290
Epoch 18/30
100/100 [==============================] - 54s 541ms/step - loss: 0.1983 - acc: 0.9220 - val_loss: 0.5245 - val_acc: 0.7280
Epoch 19/30
100/100 [==============================] - 57s 569ms/step - loss: 0.1736 - acc: 0.9385 - val_loss: 1.1733 - val_acc: 0.7280
Epoch 20/30
100/100 [==============================] - 54s 536ms/step - loss: 0.1627 - acc: 0.9450 - val_loss: 0.4002 - val_acc: 0.7350
Epoch 21/30
100/100 [==============================] - 55s 554ms/step - loss: 0.1478 - acc: 0.9505 - val_loss: 0.6728 - val_acc: 0.7220
Epoch 22/30
100/100 [==============================] - 55s 549ms/step - loss: 0.1245 - acc: 0.9595 - val_loss: 0.3836 - val_acc: 0.7370
Epoch 23/30
100/100 [==============================] - 54s 539ms/step - loss: 0.1059 - acc: 0.9690 - val_loss: 0.2034 - val_acc: 0.7250
Epoch 24/30
100/100 [==============================] - 55s 547ms/step - loss: 0.0996 - acc: 0.9680 - val_loss: 0.7567 - val_acc: 0.7390
Epoch 25/30
100/100 [==============================] - 55s 547ms/step - loss: 0.0773 - acc: 0.9750 - val_loss: 1.1593 - val_acc: 0.7400
Epoch 26/30
100/100 [==============================] - 55s 545ms/step - loss: 0.0689 - acc: 0.9820 - val_loss: 1.4114 - val_acc: 0.7250
Epoch 27/30
100/100 [==============================] - 57s 567ms/step - loss: 0.0569 - acc: 0.9845 - val_loss: 0.8993 - val_acc: 0.7100
Epoch 28/30
100/100 [==============================] - 56s 558ms/step - loss: 0.0473 - acc: 0.9870 - val_loss: 1.4554 - val_acc: 0.7310
Epoch 29/30
100/100 [==============================] - 57s 567ms/step - loss: 0.0456 - acc: 0.9850 - val_loss: 0.9483 - val_acc: 0.7310
Epoch 30/30
100/100 [==============================] - 57s 565ms/step - loss: 0.0339 - acc: 0.9935 - val_loss: 2.1054 - val_acc: 0.7100

训练完成后,把保存模型。命名为cats_and_dogs_small_1.h5,默认保存到对应的编译文件夹。

model.save('cats_and_dogs_small_1.h5')

训练过程中模型对训练和验证数据的损失(loss)和准确性(accuracy)数据对比图

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(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')#训练集和验证集准确性
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')#训练集和验证集损失
plt.legend()

plt.show()

如下图所示:
这些曲线图具有过度拟合的特点。随着时间的推移,我们的训练准确率呈线性增长,直到接近100%,而我们的验证准确率则停滞在70%多一点。我们的验证损失在五个阶段后达到最小,然后停止,而训练损失保持线性下降,直到接近0。
在这里插入图片描述
因为我们只有相对较少的训练样本(2000张),所以过度拟合将是我们最关心的问题。
过拟合(over-fitting)也称为过学习,它的直观表现是算法在训练集上表现好,但在测试集上表现不好,泛化性能差。过拟合是在模型参数拟合过程中由于训练数据包含抽样误差,在训练时复杂的模型将抽样误差也进行了拟合导致的。

数据增强

数据增强(data augmentation),意思是让有限的数据通过某种变换操作产生更多的等价数据的过程。数据增强主要用来防止过拟合,用于数据较小的时候。

过拟合使得我们无法训练一个能够推广到新数据的模型。给定无限的数据,我们的模型将暴露在手头数据分布的所有可能方面:我们永远不会过度拟合。数据增强采用的方法是从现有的训练样本中生成更多的训练数据,通过一些随机变换“增强”样本,从而生成可信的图像。我们的目标是在训练的时候,我们的模特永远不会看到完全一样的画面两次。这有助于模型暴露于数据的更多方面,并更好地进行泛化。
在Keras中,这可以通过配置要对ImageDataGenerator实例读取的图像执行的一些随机转换来完成。

参数详解
1、rotation_range是以度(0-180)为单位的值,它是随机旋转图片的范围。
2、width_shift和height_shift是范围(占总宽度或高度的一小部分),用于纵向或横向随机转换图片。
3、shear_range用于随机剪切变换。
4、zoom_range用于随机放大图片内容。
5、horizontal_flip用于在没有水平不对称假设(例如真实世界图片)的情况下水平地随机翻转一半图像。
6、fill_mode是用于填充新创建的像素的策略,可以在旋转或宽/高移位后显示。

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')

看看增强后的图像:

# This is module with image preprocessing utilities
from keras.preprocessing import image

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

# We pick one image to "augment"
img_path = fnames[3]

# Read the image and resize it
img = image.load_img(img_path, target_size=(150, 150))

# Convert it to a Numpy array with shape (150, 150, 3)
x = image.img_to_array(img)

# Reshape it to (1, 150, 150, 3)
x = x.reshape((1,) + x.shape)

# The .flow() command below generates batches of randomly transformed images.
# It will loop indefinitely, so we need to `break` the loop at some point!
i = 0
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0]))
    i += 1
    if i % 4 == 0:
        break

plt.show()

如下图所示:
在这里插入图片描述

我们使用这种数据增强配置训练一个新的网络,将永远不会看到两次相同的输入。然而,它看到的输入仍然是高度相关的,因为它们来自于少量的原始图像——我们不能产生新的信息,我们只能重新混合现有的信息。因此,这可能还不足以完全摆脱过度装修。为了进一步克服过拟合,我们还将在模型中的密接分类器(densely-connected)前添加一个Dropout:

model = models.Sequential()
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.Dropout(0.5))
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'])

使用数据增强(data augmentation)和dropout来训练我们的网络

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,)

# Note that the validation data should not be augmented!
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        #所有的图像大小会被转换成150x150
        target_size=(150, 150),
        batch_size=32,
        #由于这是一个二元分类问题,y的label值也会被转换成二元的标签
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=32,
        class_mode='binary')

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=50)

运行结果如下:
真的跑了很久很久很久,要做好心理准备。

Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Epoch 1/100
100/100 [==============================] - 86s 858ms/step - loss: 0.6924 - acc: 0.5091 - val_loss: 0.6648 - val_acc: 0.5584
Epoch 2/100
100/100 [==============================] - 82s 824ms/step - loss: 0.6809 - acc: 0.5628 - val_loss: 0.6982 - val_acc: 0.6031
Epoch 3/100
100/100 [==============================] - 87s 869ms/step - loss: 0.6648 - acc: 0.6010 - val_loss: 0.6746 - val_acc: 0.6085
Epoch 4/100
100/100 [==============================] - 84s 839ms/step - loss: 0.6451 - acc: 0.6168 - val_loss: 0.6111 - val_acc: 0.6244
 ……
Epoch 98/100
100/100 [==============================] - 92s 925ms/step - loss: 0.3324 - acc: 0.8520 - val_loss: 0.4216 - val_acc: 0.8202
Epoch 99/100
100/100 [==============================] - 92s 920ms/step - loss: 0.3470 - acc: 0.8489 - val_loss: 0.3072 - val_acc: 0.8261
Epoch 100/100
100/100 [==============================] - 92s 921ms/step - loss: 0.3450 - acc: 0.8460 - val_loss: 0.4872 - val_acc: 0.8338

训练完成后,把保存模型。命名为cats_and_dogs_small_2.h5,默认保存到对应的编译文件夹。

model.save('cats_and_dogs_small_2.h5')

训练过程中模型对训练和验证数据的损失(loss)和准确性(accuracy)数据对比图

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

运行结果如下:
在这里插入图片描述

由于数据增加和Dropout,不再过拟合:训练曲线非常接近于验证曲线。我们现在能够达到83%的精度,比非正则模型相对提高了15%。
通过进一步利用正则化技术并通过调整网络参数(例如每个卷积层的滤波器数量或网络层数量),我们可能获得更好的精度,可能高达86-87%。

参考教程:https://github.com/fchollet/deep-learning-with-python-notebooks/blob/master/5.2-using-convnets-with-small-datasets.ipynb

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