Keras TensorFlow教程:使用自己的数据集进行训练

大多数Keras教程都尝试使用图像分类数据集(如MNIST(手写识别)或基本对象CIFAR-10(基本对象识别))来开启Keras库的基础知识学习。

这篇文章将对Keras入门教程进行不同的尝试。使用自定义数据集训练第一个神经网络和卷积神经网络——将深度学习应用您自己的数据集,而不是Keras内置的数据集。

使用Keras训练您的第一个简单神经网络不需要很多代码,但是我们将逐步开始,逐步进行,以确保您了解如何在自己的自定义数据集上训练网络的过程。

今天介绍的步骤包括:

1. 安装Keras和其依赖库
2. 从磁盘加载数据
3. 训练和测试数据分组
4. 定义模型
5. 编译模型
6. 训练模型
7. 评估模型
8. 使用训练的模型进行预测

这看起来似乎需要很多步骤,但是我向您保证,一旦我们开始使用示例,您将看到示例是线性的、直观的,并且将帮助您了解使用Keras训练神经网络的基础知识。

实质上,使用MINST或者CIFARr10训练集相当于只是调用了内置的函数,训练、测试数据分组创建模型等都已预定义好, 当我们尝试使用自己的数据集时,我们可能会抓耳挠腮考虑下边几个问题:

  • 这些辅助函数从何处加载数据?
  • 磁盘上的数据集应采用什么格式?
  • 如何将数据集加载到内存中?
  • 我需要执行哪些预处理步骤?

在此Keras教程中,我们将使用名为“动物数据集”的自定义数据集。

1. 此数据集的目的是将图像正确分类: 猫 小狗 大熊猫

使用此自定义数据集可以使您了解:

1. 如何在磁盘上组织数据集
2. 如何从磁盘加载图像和分类标签
3. 如何将数据划分为训练和测试数据
4. 如何在训练数据上训练您的第一个Keras神经网络
5. 如何根据测试数据评估模型
6. 如何在训练和测试划分之外的全新数据上复用训练过的模型

按照本Keras教程中的步骤进行操作,只要您使用下面详述的项目/目录结构,就可以将“动物”数据集换成您选择的任何数据集。

抓取数据集可参考:

  • Bing Images
  • Google Images

2. 项目结构

Keras TensorFlow教程:使用自己的数据集进行训练_第1张图片cats、dogs、panda文件夹下文件数至少1000个,并且相同。某个文件夹下文件多可能导致训练出的模型有偏差;

3. 开始写我们的代码

(1)配置环境

  • ubuntu上 TensorFlow2.0安装:

  • windows上 TensorFlow2.0安装:

pip install tensorflow==2.0.0 # or tensorflow-gpu==2.0.0

(2)简单神经网络模型及卷积神经网络模型

  • train_simple_nn.py 第一个简单神经网络的模型
  • train_vgg.py 更完整的SimpleVGGNet的卷积网络模型
# train_simple_nn.py

# 训练第一个简单的 Keras 模型

# Python的必备绘图包  我们指定matplotlib  使用“ Agg” 后使我们能够将绘图保存到磁盘
import matplotlib

matplotlib.use("Agg")

# 导入必要的模块包
# scikit-learn库将帮助我们对标签进行二值化处理,拆分数据以进行训练/测试以及在终端生成训练报告
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# 您正在阅读本教程以了解Keras,它是TensorFlow和其他深度学习后端的高级前端, 2017年Keras并入了TensorFlow
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
# imutils 便利功能包 我们将使用paths 生成用于训练的图像文件路径列表
from imutils import paths
import matplotlib.pyplot as plt
# numpy Python中进行数值处理的包 必备包之一
import numpy as np
# argparse 模块是内置于Python中的模块,将动态处理命令行提供的信息
import argparse
import random
import pickle
import cv2
import os

# 1. 构建命令行参数
# --dataset   磁盘上图像数据集的路径
# --model     训练得到的模型的输出路径
# --label-bin 序列化的二进制标签文件的输出路径
# --plot      输出训练图(精确度、损失)图像文件的路径,我们将通过该图判断模型拟合是否 过度/不足
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required=True,
                help="path to input dataset of images")
ap.add_argument("-m", "--model", required=True,
                help="path to output trained model")
ap.add_argument("-l", "--label-bin", required=True,
                help="path to output label binarizer")
ap.add_argument("-p", "--plot", required=True,
                help="path to output accuracy/loss plot")
args = vars(ap.parse_args())

# 2. 从磁盘加载数据
print("[INFO] loading images...")
# 初始化 数据和分类标签列表
data = []
labels = []

# 抓取imagePaths 并随机洗牌(randomly shuffle)
# paths.list_images() 很方便的返回路径中的所有图片列表   播下种子因此随机重排序是可重现的。
imagePaths = sorted(list(paths.list_images(args["dataset"])))
random.seed(42)
random.shuffle(imagePaths)

# 循环遍历所有的图片
for imagePath in imagePaths:
    # 加载图像,忽略宽高比缩放到 32*32 像素,展平数据为 32*32*3 = 3072 像素图像, 并存储到list中
    # resize数据是至关重要的,因为neural network需要这样的维度,不同神经网络需要不同的维度
    # 展平数据使我们可以轻松地将原始像素强度传递到输入层神经元。稍后您会看到,对于VGGNet,我们将卷传递到网络,因为它是卷积的。该示例只是一个简单的非卷积网络-我们将在后文中介绍一个更高级的示例
    image = cv2.imread(imagePath)
    image = cv2.resize(image, (32, 32)).flatten()
    data.append(image)
    # 从图像路径中提取分类标签并存储到分类标签数组中
    label = imagePath.split(os.path.sep)[-2]
    labels.append(label)

# 将像素强度的缩放范围从[0,255]缩放到[0,1](常见的预处理步骤)。转换array 为 NumPy数组
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)

# 3. 构建训练和测试数据分组
# 将数据划分为75%的训练数据和25%的测试数据
# 通常分配一定比例的数据用于训练,而分配较小比例的数据用于测试。 scikit-learn提供了方便的train_test_split 函数为我们拆分数据
(trainX, testX, trainY, testY) = train_test_split(data,
                                                  labels, test_size=0.25, random_state=42)

# 我们的分类标签当前以字符串表示; 但是,Keras会假定两者都:标签被编码为整数  而且,对这些标签执行单热编码,使每个标签表示为矢量而不是整数
# 要完成此编码,我们可以使用scikit-learn 中的LabelBinarizer
# 将标签从 integer 转换为 vector
# 初始化LabelBinaries 调用fit_transform zai trainY中找到所有唯一的类标签并将他们转换为单热编码的标签
# testY 上执行单热编码
# 由于数组中只有一个是 1,因此成为单热编码
# [1, 0, 0] # corresponds to cats
# [0, 1, 0] # corresponds to dogs
# [0, 0, 1] # corresponds to panda
lb = LabelBinarizer()
trainY = lb.fit_transform(trainY)
testY = lb.transform(testY)

# 4. 定义您的Keras模型架构
# 使用Keras定义我们的神经网络架构。在这里,我们将使用一个具有一个输入层,两个隐藏层和一个输出层的网络:
# define the 3072-1024-512-3 architecture using Keras
#            32*32*3 = 3072 拼合的输入图像中的像素 第一个隐藏层由1024节点 第二个隐藏层有512节点 最终输出层中的节点数将为可能的类标签的数目-
#            在这种情况下,输出层将具有三个节点,每个节点对应一个类标签(“ cats”,“ dogs” ”和“ panda”)。
model = Sequential()
model.add(Dense(1024, input_shape=(3072,), activation="sigmoid"))
model.add(Dense(512, activation="sigmoid"))
model.add(Dense(len(lb.classes_), activation="softmax"))

# 5. 编译Keras模型
# 初始化学习速度和要训练的时期总数
INIT_LR = 0.01
EPOCHS = 80
# 使用随机梯度下降(SGD Stochastic Gradient Descent)优化器编译模型,“categorical_crossentropy”作为损失函数
# 分类交叉熵(Categorical cross-entropy)被用作几乎所有训练执行分类的网络的损失函数。
# 唯一的例外是2-class classification (2类分类 其中只有两个可能的类标签),在这种情况下,选择“ binary_crossentropy”
print("[INFO] training network...")
opt = SGD(lr=INIT_LR)
model.compile(loss="categorical_crossentropy", optimizer=opt,
              metrics=["accuracy"])

# 6. “拟合”Keras模型使其适合数据
# 训练神经网络
# batch_size 控制通过网络传递的每组数据的大小。较大的GPU将能够容纳较大的批处理大小。建议从32或者64开始,然后逐步增加
H = model.fit(x=trainX, y=trainY, validation_data=(testX, testY),
              epochs=EPOCHS, batch_size=32)

# 7. 评估Keras模型
# 我们已经训练了实际模型,现在我们需要根据测试数据对其进行评估。
# 重要的是,我们必须对测试数据进行评估,以便我们能够对模型进行从未被训练过的数据进行无偏(或尽可能接近无偏)表示。
# 要评估我们的Keras模型,我们可以结合使用.predict 模型的方法以及scikit-learn 中的category_report :
print("[INFO] evaluating network...")
predictions = model.predict(x=testX, batch_size=32)
print(classification_report(testY.argmax(axis=1),
                            predictions.argmax(axis=1), target_names=lb.classes_))
# plot the training loss and accuracy
N = np.arange(0, EPOCHS)
plt.style.use("ggplot")
plt.figure()
plt.plot(N, H.history["loss"], label="train_loss")
plt.plot(N, H.history["val_loss"], label="val_loss")
plt.plot(N, H.history["accuracy"], label="train_acc")
plt.plot(N, H.history["val_accuracy"], label="val_acc")
plt.title("Training Loss and Accuracy (Simple NN)")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend()
plt.savefig(args["plot"])

# 8.保存模型和二进制标签文件
print("[INFO] serializing network and label binarizer...")
model.save(args["model"], save_format="h5")
f = open(args["label_bin"], "wb")
f.write(pickle.dumps(lb))
f.close()

(3)第一个卷积神经网络

train_vgg.py

# 训练第一个卷积神经网络
# python train_vgg.py --dataset animals --model output/smallvggnet.model \
# 	--label-bin output/smallvggnet_lb.pickle \
# 	--plot output/smallvggnet_plot.png


# 在Google相册中,如果您在搜索框中输入“ dog”,则将返回照片库中的狗的图片-我很确定该图片搜索引擎功能已使用CNN。
# 图像搜索引擎并不是CNN的唯一用例-我敢打赌,您的想法已经开始提出各种可应用于深度学习的想法。
#
# SmallVggnet 精确度76% 高于 之前的普通模型 60%

# 设置matplotlib为Agg,因此图像可以保存在磁盘上
import matplotlib
matplotlib.use("Agg")
# 导入必要的包
from pyimagesearch.smallvggnet import SmallVGGNet
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# 我们将使用ImageDataGenerator扩充数据。我几乎总是建议使用数据扩充,这样会导致模型更好地推广。
# 数据扩充涉及对现有训练数据添加随机旋转,平移,剪切和缩放比例。
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.optimizers import SGD
from imutils import paths
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import pickle
import cv2
import os

# 构建命令行参数
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required=True,
                help="path to input dataset of images")
ap.add_argument("-m", "--model", required=True,
                help="path to output trained model")
ap.add_argument("-l", "--label-bin", required=True,
                help="path to output label binarizer")
ap.add_argument("-p", "--plot", required=True,
                help="path to output accuracy/loss plot")
args = vars(ap.parse_args())

# --dataset: 磁盘上图像数据集的路径
# --model: 我们的模型将序列化并输出到磁盘。此参数包含输出模型文件的路径。确保对模型进行相应命名,以免覆盖以前训练过的模型(例如简单的神经网络模型)。
# --label-bin: 数据集标签被序列化到磁盘的路径
# --plot:输出训练图图像文件的路径。我们将检查该图,以检查数据是否过度/不足。每次通过更改参数来训练模型时,都应在命令行中指定不同的绘图文件名,以便在笔记本或注释文件中拥有与训练笔记相对应的绘图历史记录。

# 初始化数据和标签类
print("[INFO] loading images...")
data = []
labels = []

# 获取图像路径并随机洗牌(给定42的随机种子,以重新结果)
imagePaths = sorted(list(paths.list_images(args["dataset"])))
random.seed(42)
random.shuffle(imagePaths)

# 循环遍历输入图像
for imagePath in imagePaths:

    # 加载图像,resize到64*64(SmallVGGNet所需的输入空间尺寸) 并存储道list中
    image = cv2.imread(imagePath)
    image = cv2.resize(image, (64, 64))
    # 一个关键的区别是我们不会为神经网络拼合数据,因为它是卷积的
    data.append(image)

    # 从图像路径中提取分类标签并添加到list中
    label = imagePath.split(os.path.sep)[-2]
    labels.append(label)

# scale 像素强度到 [0,1]
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)

# 分组训练数据(75%)和测试数据(25%)一个建议是80/20比例去看一下是否有明显提高
(trainX, testX, trainY, testY) = train_test_split(data,
                                                  labels, test_size=0.25, random_state=42)

# 初始化类标签器 将类标签器从整数转换为向量
# 标签二值化这样后续可以进行一键热编码,并在脚本的后面将标签二值化器序列化为pickle文件
lb = LabelBinarizer()
trainY = lb.fit_transform(trainY)
testY = lb.transform(testY)

# 构建图像加强生成器
# 图像增强使我们可以通过随机旋转,移动,剪切,缩放和翻转从现有的训练数据中构建“其他”训练数据。
# 数据扩充通常是以下关键步骤:
# -避免过度拟合
# -确保模型能很好地泛化  我建议您始终执行数据增强,除非您有明确的理由不这样做。
aug = ImageDataGenerator(rotation_range=30, width_shift_range=0.1,
                         height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,
                         horizontal_flip=True, fill_mode="nearest")

# 初始化 构建 VGG-like Convolutional Neural Network
model = SmallVGGNet.build(width=64, height=64, depth=3,
                          classes=len(lb.classes_))

# 初始化初始学习率,训练次数,训练批次
INIT_LR = 0.01
EPOCHS = 75
BS = 32

# initialize the model and optimizer (you'll want to use
# binary_crossentropy for 2-class classification)
# 编译模型
# 初始化模型及在二值分类中的二值交叉熵优化器
print("[INFO] training network...")
opt = SGD(lr=INIT_LR, decay=INIT_LR / EPOCHS)
model.compile(loss="categorical_crossentropy", optimizer=opt,
              metrics=["accuracy"])

# 训练网络
# model.fit 可同时处理训练和即时扩充的增强数据。
# 我们必须将训练数据作为第一个参数传递给生成器。生成器将根据我们先前进行的设置生成批量的增强训练数据。
H = model.fit(x=aug.flow(trainX, trainY, batch_size=BS),
              validation_data=(testX, testY), steps_per_epoch=len(trainX) // BS,
              epochs=EPOCHS)

# 评估网络模型
print("[INFO] evaluating network...")
predictions = model.predict(x=testX, batch_size=32)
print(classification_report(testY.argmax(axis=1),
                            predictions.argmax(axis=1), target_names=lb.classes_))

# 画出训练精确度和损失图
N = np.arange(0, EPOCHS)
plt.style.use("ggplot")
plt.figure()
plt.plot(N, H.history["loss"], label="train_loss")
plt.plot(N, H.history["val_loss"], label="val_loss")
plt.plot(N, H.history["accuracy"], label="train_acc")
plt.plot(N, H.history["val_accuracy"], label="val_acc")
plt.title("Training Loss and Accuracy (SmallVGGNet)")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend()
plt.savefig(args["plot"])

# 保存模型和类标签器到磁盘
print("[INFO] serializing network and label binarizer...")
model.save(args["model"], save_format="h5")
f = open(args["label_bin"], "wb")
f.write(pickle.dumps(lb))
f.close()

(4)ImageDataGenerator

使用ImageDataGenerator扩充数据,是为了使模型更好地推广,泛化


数据扩充涉及对现有训练数据添加随机旋转,平移,剪切和缩放比例。

(5)SGD 随机梯度下降损失函数

使用随机梯度下降(SGD Stochastic Gradient Descent)优化器编译模型,“categorical_crossentropy”作为损失函数


分类交叉熵(Categorical cross-entropy)被用作几乎所有训练执行分类的网络的损失函数。
唯一的例外是2-class classification (2类分类 其中只有两个可能的类标签),在这种情况下,选择“ binary_crossentropy”.

(6) 简单的神经网络 与 卷积神经网络训练结果对比

简单神经网络 卷积神经网络
训练速度 快 3s/Epoch 慢 50s/Step
准确度 近60% 75%
泛化能力

简单的神经网络训练速度快,精确度稍低;
卷积神经网络训练速度慢,精确度高,由于数据集经过了数据增强具有比较好的泛化能力;

同样的数据下的预测精确度:
简单神经网络预测结果:
Keras TensorFlow教程:使用自己的数据集进行训练_第2张图片Keras TensorFlow教程:使用自己的数据集进行训练_第3张图片
卷积神经网络预测结果:
Keras TensorFlow教程:使用自己的数据集进行训练_第4张图片Keras TensorFlow教程:使用自己的数据集进行训练_第5张图片
简单神经网络训练损失/精确度图:
Keras TensorFlow教程:使用自己的数据集进行训练_第6张图片卷积神经网络损失/精确度图:
Keras TensorFlow教程:使用自己的数据集进行训练_第7张图片

ps: 啃英文对我来说有点难,又有点简单。这是我大学无比喜欢上的课,后来也会记单词,英汉互译的看诗集。这一篇文章确实有点难啃,有些学术名词我不是很懂,不过我在努力,学习的感觉真的太好了。
晚安~~~

参考: https://www.pyimagesearch.com/2018/09/10/keras-tutorial-how-to-get-started-with-keras-deep-learning-and-python/

你可能感兴趣的:(Python,OpenCV,图像处理,TensorFlow)