介绍
基于卷积神经网络(CNN)的猫狗图片分类项目是机器学习领域中的一种常见任务,它涉及图像处理和深度学习技术。以下是该项目的技术点和流程介绍:
技术点
- 卷积神经网络 (CNN): CNN 是一种专门用于处理具有类似网格结构的数据的神经网络模型,比如时间序列数据或二维的图像数据。CNN 的核心组件包括卷积层、池化层和全连接层。
- 卷积层: 卷积层使用滤波器(或称内核)在输入图像上滑动,并计算局部加权和来检测图像中的特征,如边缘、纹理等。
- 激活函数: 激活函数(如ReLU, Sigmoid等)引入非线性因素,使得网络可以学习到更复杂的模式。
- 池化层: 通常采用最大池化或平均池化操作,用于减少空间尺寸,降低参数数量,同时保留最重要的信息。
- 全连接层: 在最后几层,CNN 会将多维的特征图展平为一维向量,并通过全连接层进行分类预测。
- 损失函数与优化算法: 使用交叉熵作为损失函数,通过反向传播和梯度下降等优化算法来更新网络权重。
- 正则化技术: 包括L2正则化、Dropout等,以防止过拟合。
- 数据增强: 通过对原始图像进行旋转、翻转、缩放等变换生成更多的训练样本,增加模型的泛化能力。
- 预训练模型: 可以利用已经在大规模数据集上训练好的模型(如VGG16, ResNet等),并在此基础上进行迁移学习。
流程
- 数据收集: 收集大量标注好的猫和狗的图片作为训练集和测试集。
- 数据预处理: 对图像进行标准化(如调整大小、归一化像素值)以及可能的数据增强。
- 构建模型: 设计CNN架构,选择合适的层数、滤波器大小和其他超参数。
- 训练模型: 使用训练数据集对CNN进行训练,调整模型参数以最小化损失函数。
- 评估模型: 在验证集或独立的测试集上评估模型性能,检查准确率、召回率等指标。
- 调优模型: 根据评估结果调整模型架构或超参数,重复训练和评估过程直至满意。
- 部署模型: 将最终训练好的模型部署到生产环境中,实现对新图像的实时分类。
- 持续改进: 随着时间推移,可能会有新的数据加入,或者发现模型表现不佳的地方,这时就需要不断迭代和改进模型。
猫狗图片分类
猫狗图片分类模型是一个使用机器学习技术来辨别猫和狗图片的模型。该模型可以通过对输入的图片进行分析和比对,从而自动识别出图片中的是猫还是狗。
猫狗图片分类模型通常是基于卷积神经网络(Convolutional Neural Network,CNN)构建的。CNN是一种专门用于图像识别和处理的神经网络模型,其特点是能够自动学习图像特征并进行分类。
训练猫狗图片分类模型需要大量的带有标签的猫和狗图片作为训练数据。模型通过训练数据不断调整神经网络中的权重和参数,以提高判断猫和狗的准确性。
一旦训练完成,猫狗图片分类模型就可以用于对新的未知图片进行分类。用户只需输入一张图片,模型会自动输出猫或狗的分类结果。
猫狗图片分类模型可以应用于各种场景,比如社交媒体上的猫狗图片分享、电子商务平台上的宠物产品推荐等。该模型的准确性和性能取决于训练数据的质量和模型的设计。因此,对于更高准确性的要求,需要更多的训练数据和更复杂的模型设计。
导包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
import os
加载数据
base_dir = './data/cats_and_dogs'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
# 训练集
train_cats_dir = os.path.join(train_dir, 'cats')
train_dogs_dir = os.path.join(train_dir, 'dogs')
# 验证集
validation_cats_dir = os.path.join(validation_dir, 'cats')
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
数据处理
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# ImageDataGenerator 图片生成器, 会自动的帮我们从指定目录中读取图片.
train_datagen = ImageDataGenerator(rescale=1./255)
valid_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
train_dir, # 文件夹路径
target_size=(64, 64), # 指定图片缩放之后的大小
batch_size=20,
# 默认是categorical,表示多分类, 二分类用binary
class_mode='binary')
validation_generator = valid_datagen.flow_from_directory(
validation_dir, # 文件夹路径
target_size=(64, 64), # 指定图片缩放之后的大小
batch_size=20,
# 默认是categorical,表示多分类, 二分类用binary
class_mode='binary')
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
构建CNN网络
网络结构
- Sequential模型:
- 使用tf.keras.models.Sequential()创建一个线性堆叠的模型,意味着每一层都有一个输入张量和一个输出张量,并且这些层是顺序连接的。
- 第一组卷积层与池化层:
- 第一个Conv2D层使用32个3x3的滤波器进行卷积操作,padding='same’保证了输出特征图与输入图像大小一致,激活函数采用ReLU。
- 输入形状被指定为(64, 64, 3),表示输入图片尺寸为64x64像素,具有3个颜色通道(RGB)。
- 第二个Conv2D层继续应用32个3x3的滤波器,进一步提取特征。
- MaxPool2D层通过2x2窗口的最大值池化减少特征图的空间维度,从而降低计算复杂度并控制过拟合。
- 第二组卷积层与池化层:
- 接下来增加了两个Conv2D层,每个都使用64个3x3的滤波器,这有助于捕捉更复杂的模式。
- 再次应用MaxPool2D以减小空间维度。
- 第三组卷积层与池化层:
- 类似地,这一部分包含两个Conv2D层,这次是128个3x3的滤波器,用于捕捉更加抽象的特征。
- 最后一个MaxPool2D再次缩小特征图。
- 全连接层(密集层):
- Flatten()将三维的特征图转换成一维向量,以便于后续的全连接层处理。
- 第一个Dense层有512个神经元,并使用ReLU作为激活函数,提供非线性的映射能力。
- 第二个Dense层有256个神经元,同样使用ReLU激活。
- 输出层:
最后一层是一个具有单个神经元的Dense层,其激活函数为sigmoid。这是因为这是一个二分类问题,sigmoid函数可以将输出压缩到[0, 1]区间内,表示预测属于某一类的概率。
# 搭建卷积神经网络
model = tf.keras.models.Sequential()
# 2次卷积一次池化, 3层, 2层全连接.
model.add(tf.keras.layers.Conv2D(filters=32,
kernel_size=3,
padding='same',
activation='relu',
input_shape=(64, 64, 3)))
model.add(tf.keras.layers.Conv2D(filters=32,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.MaxPool2D(pool_size=2))
model.add(tf.keras.layers.Conv2D(filters=64,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.Conv2D(filters=64,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.MaxPool2D(pool_size=2))
model.add(tf.keras.layers.Conv2D(filters=128,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.Conv2D(filters=128,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.MaxPool2D(pool_size=2))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(512, activation='relu'))
model.add(tf.keras.layers.Dense(256, activation='relu'))
model.add(tf.keras.layers.Dense(1, activation='sigmoid'))
model.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_6 (Conv2D) (None, 64, 64, 32) 896
_________________________________________________________________
conv2d_7 (Conv2D) (None, 64, 64, 32) 9248
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 32, 32, 32) 0
_________________________________________________________________
conv2d_8 (Conv2D) (None, 32, 32, 64) 18496
_________________________________________________________________
conv2d_9 (Conv2D) (None, 32, 32, 64) 36928
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 16, 16, 64) 0
_________________________________________________________________
conv2d_10 (Conv2D) (None, 16, 16, 128) 73856
_________________________________________________________________
conv2d_11 (Conv2D) (None, 16, 16, 128) 147584
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 8, 8, 128) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 8192) 0
_________________________________________________________________
dense (Dense) (None, 512) 4194816
_________________________________________________________________
dense_1 (Dense) (None, 256) 131328
_________________________________________________________________
dense_2 (Dense) (None, 1) 257
=================================================================
Total params: 4,613,409
Trainable params: 4,613,409
Non-trainable params: 0
_________________________________________________________________
model.compile(loss='binary_crossentropy',
optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
metrics=['acc'])
模型训练
history = model.fit(train_generator,
steps_per_epoch=100, # 2000 images : = batch_size * steps
epochs=20,
validation_data=validation_generator,
validation_steps=50, # 1000 images = batch_size * steps
)
Epoch 1/20
100/100 [==============================] - 4s 30ms/step - loss: 0.6933 - acc: 0.5003 - val_loss: 0.6881 - val_acc: 0.5060
Epoch 2/20
100/100 [==============================] - 3s 28ms/step - loss: 0.6768 - acc: 0.5654 - val_loss: 0.6393 - val_acc: 0.6350
Epoch 3/20
100/100 [==============================] - 3s 27ms/step - loss: 0.6194 - acc: 0.6684 - val_loss: 0.6204 - val_acc: 0.6710
Epoch 4/20
100/100 [==============================] - 3s 28ms/step - loss: 0.5548 - acc: 0.7294 - val_loss: 0.6026 - val_acc: 0.6830
Epoch 5/20
100/100 [==============================] - 3s 27ms/step - loss: 0.5452 - acc: 0.7311 - val_loss: 0.5963 - val_acc: 0.6780
Epoch 6/20
100/100 [==============================] - 3s 27ms/step - loss: 0.5030 - acc: 0.7309 - val_loss: 0.5598 - val_acc: 0.7350
Epoch 7/20
100/100 [==============================] - 3s 28ms/step - loss: 0.4529 - acc: 0.7920 - val_loss: 0.5633 - val_acc: 0.7250
Epoch 8/20
100/100 [==============================] - 3s 27ms/step - loss: 0.4246 - acc: 0.8073 - val_loss: 0.5689 - val_acc: 0.7230
Epoch 9/20
100/100 [==============================] - 3s 27ms/step - loss: 0.4018 - acc: 0.8248 - val_loss: 0.5549 - val_acc: 0.7270
Epoch 10/20
100/100 [==============================] - 3s 27ms/step - loss: 0.3472 - acc: 0.8424 - val_loss: 0.5491 - val_acc: 0.7370
Epoch 11/20
100/100 [==============================] - 3s 28ms/step - loss: 0.3243 - acc: 0.8573 - val_loss: 0.5775 - val_acc: 0.7280
Epoch 12/20
100/100 [==============================] - 3s 28ms/step - loss: 0.2770 - acc: 0.8870 - val_loss: 0.6722 - val_acc: 0.7200
Epoch 13/20
100/100 [==============================] - 3s 27ms/step - loss: 0.2264 - acc: 0.9046 - val_loss: 0.6584 - val_acc: 0.7470
Epoch 14/20
100/100 [==============================] - 3s 27ms/step - loss: 0.1956 - acc: 0.9247 - val_loss: 0.6828 - val_acc: 0.7400
Epoch 15/20
100/100 [==============================] - 3s 27ms/step - loss: 0.1288 - acc: 0.9508 - val_loss: 0.7735 - val_acc: 0.7290
Epoch 16/20
100/100 [==============================] - 3s 28ms/step - loss: 0.1085 - acc: 0.9670 - val_loss: 0.7551 - val_acc: 0.7400
Epoch 17/20
100/100 [==============================] - 3s 27ms/step - loss: 0.1228 - acc: 0.9540 - val_loss: 0.9157 - val_acc: 0.7300
Epoch 18/20
100/100 [==============================] - 3s 27ms/step - loss: 0.0789 - acc: 0.9710 - val_loss: 0.9311 - val_acc: 0.7560
Epoch 19/20
100/100 [==============================] - 3s 28ms/step - loss: 0.0217 - acc: 0.9979 - val_loss: 0.9941 - val_acc: 0.7270
Epoch 20/20
100/100 [==============================] - 3s 27ms/step - loss: 0.0207 - acc: 0.9946 - val_loss: 1.0781 - val_acc: 0.7470
可视化
pd.DataFrame(history.history).plot(figsize=(8, 5))
plt.grid()
plt.gca().set_ylim(0, 1)
plt.show()
数据增强
ImageDataGenerator 提供了一种简单的方法来应用各种图像变换,以扩充训练数据集,从而提高模型的泛化能力。对于猫狗分类这样的二分类任务,适当的图像变换可以帮助模型学习到更多不变性特征。
- rescale=1./255: 将所有像素值从0-255缩放到0-1之间,这是为了标准化输入数据,使得梯度下降更稳定。
- rotation_range=40: 随机旋转图片的角度范围(单位是度),这里设置为40度,意味着图片可能会被随机旋转最多正负40度。
- width_shift_range=0.2 和 height_shift_range=0.2: 宽度和高度方向上的随机平移范围,这里的值表示相对于总宽度或高度的比例,即图像可能在水平或垂直方向上移动最多20%。
- shear_range=0.2: 斜切变换的程度,可以模拟一些透视效果。
- zoom_range=0.2: 随机缩放范围,这里的值也表示比例,即图像可能会被放大或缩小最多20%。
- horizontal_flip=True: 随机水平翻转图像,这对于左右对称的对象(如猫和狗)特别有用。
- fill_mode=‘nearest’: 当应用上述变换时,如果图像超出原始边界,则用最邻近填充模式填补新产生的空白区域。
创建数据生成器
flow_from_directory 方法可以从文件夹中读取图像,并自动根据子文件夹的名字给图像打上标签。它会生成批量的图像数据,可以直接用于模型训练。
- train_dir 和 validation_dir: 分别指定了训练集和验证集所在的目录路径。每个目录应该包含两个子目录,一个用于“猫”的图片,另一个用于“狗”的图片。
- target_size=(64, 64): 所有加载的图片将被调整大小到64x64像素。这应该与CNN模型输入层定义的input_shape相匹配。
- batch_size=20: 每次迭代返回的图片数量。在这个例子中,每次会生成20张图片作为一批。
- class_mode=‘binary’: 因为这是一个二分类问题,所以这里指定为二元分类模式。如果是多分类问题,则应设置为’categorical’。
通过这种方式,你可以确保训练过程中不断有新的、经过变换的图像提供给模型,有助于提升模型的表现,特别是当可用的原始图像数量有限时。同时,验证集仅进行了简单的归一化处理,而没有进行其他变换,以保证评估结果的真实性。
# 数据增强
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,
fill_mode='nearest')
valid_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
train_dir, # 文件夹路径
target_size=(64, 64), # 指定图片缩放之后的大小
batch_size=20,
# 默认是categorical,表示多分类, 二分类用binary
class_mode='binary')
validation_generator = valid_datagen.flow_from_directory(
validation_dir, # 文件夹路径
target_size=(64, 64), # 指定图片缩放之后的大小
batch_size=20,
# 默认是categorical,表示多分类, 二分类用binary
class_mode='binary')
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
再次训练
# 搭建卷积神经网络
model = tf.keras.models.Sequential()
# 2次卷积一次池化, 3层, 2层全连接.
model.add(tf.keras.layers.Conv2D(filters=32,
kernel_size=3,
padding='same',
activation='relu',
input_shape=(64, 64, 3)))
model.add(tf.keras.layers.Conv2D(filters=32,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.MaxPool2D(pool_size=2))
model.add(tf.keras.layers.Conv2D(filters=64,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.Conv2D(filters=64,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.MaxPool2D(pool_size=2))
model.add(tf.keras.layers.Conv2D(filters=128,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.Conv2D(filters=128,
kernel_size=3,
padding='same',
activation='relu'))
model.add(tf.keras.layers.MaxPool2D(pool_size=2))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(512, activation='relu'))
model.add(tf.keras.layers.Dense(256, activation='relu'))
model.add(tf.keras.layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
metrics=['acc'])
history = model.fit(train_generator,
steps_per_epoch=100, # 2000 images : = batch_size * steps
epochs=20,
validation_data=validation_generator,
validation_steps=50, # 1000 images = batch_size * steps
)
Epoch 1/20
100/100 [==============================] - 5s 47ms/step - loss: 0.6934 - acc: 0.4989 - val_loss: 0.6905 - val_acc: 0.5040
Epoch 2/20
100/100 [==============================] - 5s 47ms/step - loss: 0.6930 - acc: 0.5108 - val_loss: 0.6845 - val_acc: 0.5280
Epoch 3/20
100/100 [==============================] - 4s 44ms/step - loss: 0.6867 - acc: 0.5560 - val_loss: 0.6446 - val_acc: 0.6200
Epoch 4/20
100/100 [==============================] - 4s 45ms/step - loss: 0.6524 - acc: 0.6232 - val_loss: 0.6780 - val_acc: 0.5750
Epoch 5/20
100/100 [==============================] - 4s 44ms/step - loss: 0.6736 - acc: 0.5856 - val_loss: 0.6043 - val_acc: 0.6770
Epoch 6/20
100/100 [==============================] - 4s 44ms/step - loss: 0.6133 - acc: 0.6503 - val_loss: 0.5926 - val_acc: 0.6860
Epoch 7/20
100/100 [==============================] - 4s 44ms/step - loss: 0.6189 - acc: 0.6653 - val_loss: 0.6047 - val_acc: 0.6670
Epoch 8/20
100/100 [==============================] - 5s 46ms/step - loss: 0.6108 - acc: 0.6662 - val_loss: 0.5849 - val_acc: 0.6740
Epoch 9/20
100/100 [==============================] - 5s 46ms/step - loss: 0.5685 - acc: 0.6985 - val_loss: 0.5654 - val_acc: 0.7070
Epoch 10/20
100/100 [==============================] - 5s 45ms/step - loss: 0.5980 - acc: 0.6622 - val_loss: 0.5646 - val_acc: 0.6970
Epoch 11/20
100/100 [==============================] - 5s 46ms/step - loss: 0.5759 - acc: 0.6879 - val_loss: 0.5836 - val_acc: 0.6910
Epoch 12/20
100/100 [==============================] - 5s 45ms/step - loss: 0.5805 - acc: 0.6865 - val_loss: 0.5640 - val_acc: 0.7060
Epoch 13/20
100/100 [==============================] - 5s 45ms/step - loss: 0.5733 - acc: 0.6967 - val_loss: 0.5808 - val_acc: 0.6820
Epoch 14/20
100/100 [==============================] - 5s 45ms/step - loss: 0.5779 - acc: 0.6857 - val_loss: 0.6307 - val_acc: 0.6680
Epoch 15/20
100/100 [==============================] - 4s 45ms/step - loss: 0.5498 - acc: 0.7178 - val_loss: 0.5564 - val_acc: 0.7050
Epoch 16/20
100/100 [==============================] - 5s 45ms/step - loss: 0.5561 - acc: 0.7016 - val_loss: 0.6625 - val_acc: 0.6450
Epoch 17/20
100/100 [==============================] - 4s 45ms/step - loss: 0.5513 - acc: 0.7060 - val_loss: 0.5414 - val_acc: 0.7160
Epoch 18/20
100/100 [==============================] - 4s 45ms/step - loss: 0.5293 - acc: 0.7339 - val_loss: 0.5329 - val_acc: 0.7300
Epoch 19/20
100/100 [==============================] - 5s 45ms/step - loss: 0.5344 - acc: 0.7272 - val_loss: 0.5141 - val_acc: 0.7410
Epoch 20/20
100/100 [==============================] - 5s 48ms/step - loss: 0.5257 - acc: 0.7146 - val_loss: 0.5461 - val_acc: 0.7240
调参
history = model.fit(train_generator,
steps_per_epoch=100, # 2000 images : = batch_size * steps
epochs=20,
validation_data=validation_generator,
validation_steps=50, # 1000 images = batch_size * steps
)
Epoch 1/20
100/100 [==============================] - 5s 47ms/step - loss: 0.5291 - acc: 0.7345 - val_loss: 0.5312 - val_acc: 0.7310
Epoch 2/20
100/100 [==============================] - 5s 45ms/step - loss: 0.5126 - acc: 0.7400 - val_loss: 0.5147 - val_acc: 0.7490
Epoch 3/20
100/100 [==============================] - 4s 45ms/step - loss: 0.5192 - acc: 0.7385 - val_loss: 0.4972 - val_acc: 0.7420
Epoch 4/20
100/100 [==============================] - 5s 45ms/step - loss: 0.5124 - acc: 0.7465 - val_loss: 0.5264 - val_acc: 0.7170
Epoch 5/20
100/100 [==============================] - 4s 45ms/step - loss: 0.5141 - acc: 0.7485 - val_loss: 0.4969 - val_acc: 0.7450
Epoch 6/20
100/100 [==============================] - 5s 46ms/step - loss: 0.5048 - acc: 0.7400 - val_loss: 0.5044 - val_acc: 0.7460
Epoch 7/20
100/100 [==============================] - 5s 46ms/step - loss: 0.4905 - acc: 0.7610 - val_loss: 0.5057 - val_acc: 0.7400
Epoch 8/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4864 - acc: 0.7620 - val_loss: 0.4977 - val_acc: 0.7380
Epoch 9/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4972 - acc: 0.7585 - val_loss: 0.5027 - val_acc: 0.7530
Epoch 10/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4959 - acc: 0.7660 - val_loss: 0.4850 - val_acc: 0.7570
Epoch 11/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4816 - acc: 0.7655 - val_loss: 0.4954 - val_acc: 0.7440
Epoch 12/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4731 - acc: 0.7750 - val_loss: 0.5530 - val_acc: 0.7060
Epoch 13/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4826 - acc: 0.7625 - val_loss: 0.4668 - val_acc: 0.7700
Epoch 14/20
100/100 [==============================] - 4s 45ms/step - loss: 0.4625 - acc: 0.7865 - val_loss: 0.4852 - val_acc: 0.7580
Epoch 15/20
100/100 [==============================] - 4s 45ms/step - loss: 0.4678 - acc: 0.7830 - val_loss: 0.4709 - val_acc: 0.7690
Epoch 16/20
100/100 [==============================] - 5s 46ms/step - loss: 0.4590 - acc: 0.7835 - val_loss: 0.4554 - val_acc: 0.7730
Epoch 17/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4528 - acc: 0.7985 - val_loss: 0.4717 - val_acc: 0.7680
Epoch 18/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4524 - acc: 0.7755 - val_loss: 0.4518 - val_acc: 0.7750
Epoch 19/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4543 - acc: 0.7825 - val_loss: 0.4612 - val_acc: 0.7780
Epoch 20/20
100/100 [==============================] - 5s 45ms/step - loss: 0.4376 - acc: 0.8035 - val_loss: 0.5148 - val_acc: 0.7710