如何用Keras建立一个图像分类器

166 阅读12分钟

如何用Keras构建一个图像分类器

在这篇文章中,我们将学习如何使用卷积神经网络来构建一个图像分类器。我们将在后端使用Keras与TensorFlow。图像分类帮助我们识别和鉴定图像。我们在医疗保健、农业、教育、监控等领域应用图像分类器。

我们将看到我们如何在医疗保健领域应用图像分类器。我们在这里的目标是训练一个CNN模型,对COVID-19和正常病人的胸部X光扫描进行分类。

内容列表

  • 导入库和探索数据集
  • 数据可视化
  • 数据预处理和增强
  • 建立CNN模型
  • 编译和训练模型
  • 模型评估
  • 对新数据的预测

先决条件

在我们开始之前,最好能对以下内容有所了解。

  • 卷积神经网络的基础知识。
  • Python编程。
  • [Colab笔记本]。

导入库和探索数据集

让我们从导入我们将在这个项目中使用的库开始。

# Importing Libraries
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import numpy as np
import matplotlib.pyplot as plt

我们将创建一个目录,在那里我们可以存储我们的模型,因为它正在训练。让我们检查一下我们所使用的TensorFlow的版本,并确认我们是否使用GPU。

# creating models directory
import os
if not os.path.isdir('models'):
  os.mkdir('models')
# checking TensorFlow version and GPU usage
print('Tensorflow version:', tf.__version__)
print('Is using GPU?', tf.test.is_gpu_available())

alt text

我们使用的是TensorFlow 2.4,但我们的输出对GPU来说是假的。我们需要连接到GPU运行时。

要做到这一点,我们。

  1. 点击Runtime

alt text

  1. 选择改变运行时类型

alt text

  1. 选择GPU作为硬件加速器

alt text

  1. 点击保存

让我们再次运行上面的代码。

alt text

现在我们的输出是True。这表明我们在谷歌的Colab上使用了GPU。有了GPU,我们可以更快地训练我们的模型。

让我们克隆包含数据集的GitHub repo。

# Clone and Explore Dataset
!git clone https://github.com/education454/datasets.git

alt text

现在数据集在Colab中可用。

alt text

让我们把路径设置为数据集的主目录。

# setting path to the main directory
main_dir = "/content/datasets/Data"

我们还设置训练集和测试集的子目录的路径。

# Setting path to the training directory
train_dir = os.path.join(main_dir, 'train')

# Setting path to the test directory
test_dir = os.path.join(main_dir, 'test')

让我们设置训练covid图像和训练normal图像的路径。我们将对测试covid图像和测试正常图像做同样的设置。

# Directory with train covid images
train_covid_dir = os.path.join(train_dir, 'COVID19')

# Directory with train normal images
train_normal_dir = os.path.join(train_dir, 'NORMAL')

# Directory with test covid image
test_covid_dir = os.path.join(test_dir, 'COVID19')

# Directory with test normal image
test_normal_dir = os.path.join(test_dir, 'NORMAL')

让我们把每个目录下的所有图像放在一个列表中,并打印出每个列表中的前十个文件名。

# Creating a list of filenames in each directory
train_covid_names = os.listdir(train_covid_dir)
print(train_covid_names[:10])  # printing a list of the first 10 filenames

train_normal_names = os.listdir(train_normal_dir)
print(train_normal_names[:10])

test_covid_names = os.listdir(test_covid_dir)
print(test_covid_names[:10])

test_normal_names = os.listdir(test_normal_dir)
print(test_normal_names[:10])

alt text

让我们看看训练集和测试集中可用的图像总数。

# Printing total number of images present in each set
print('Total no of images in training set:', len(train_covid_names
                                                + train_normal_names))
print("Total no of images in test set:", len(test_covid_names
                                            + test_normal_names))

alt text

我们在训练集里有1,811张图片,在测试集里有484张图片。

数据的可视化

matplotlib软件包中的图像模块使我们能够加载、重新缩放和显示图像。我们绘制了一个由16张图片、8张Covid19图片和8张正常图片组成的网格。

# Data Visualization
import matplotlib.image as mpimg
# Setting the no of rows and columns
ROWS = 4
COLS = 4
# Setting the figure size
fig = plt.gcf()
# get current figure; allows us to get a reference to current figure when using pyplot
fig.set_size_inches(12, 12)

我们创建一个包含训练集中8张Covid-19图像的目录路径的列表。我们对正常类也做同样的事情。让我们合并这两个列表,得到总共16张图像,我们将展示这些图像。

# get the directory to each image file in the trainset
covid_pic = [os.path.join(train_covid_dir, filename) for filename in train_covid_names[:8]]
normal_pic = [os.path.join(train_normal_dir, filename) for filename in train_normal_names[:8]]
print(covid_pic)
print(normal_pic)
# merge covid and normal lists
merged_list = covid_pic + normal_pic
print(merged_list)

alt text

# Plotting the images in the merged list
for i, img_path in enumerate(merged_list):
    # getting the filename from the directory
    data = img_path.split('/', 6)[6]
    # creating a subplot of images with the no. of rows and colums with index no
    sp = plt.subplot(ROWS, COLS, i+1)
    # turn off axis
    sp.axis('Off')
    # reading the image data to an array
    img = mpimg.imread(img_path)
    # setting title of plot as the filename
    sp.set_title(data, fontsize=6)
    # displaying data as image
    plt.imshow(img, cmap='gray')
    
plt.show()  # display the plot

alt text

数据预处理和扩增

我们的数据集有训练数据和测试数据。没有验证数据。我们使用20%的训练数据进行验证。

我们使用训练数据来训练模型。我们使用验证数据来检查训练中的模型。我们使用测试数据来测试训练后的模型。


# Data Preprocessing and Augmentation
# Generate training, testing and validation batches
dgen_train = ImageDataGenerator(rescale=1./255,
                                validation_split=0.2,  # using 20% of training data for validation 
                                zoom_range=0.2,
                                horizontal_flip=True)
dgen_validation = ImageDataGenerator(rescale=1./255)
dgen_test = ImageDataGenerator(rescale=1./255)

图像增强有助于我们增加训练集的大小。它也有助于减少过度拟合。ImageDataGenerator()类通过实时数据增强生成成批的张量图像数据。

我们创建该类的对象。一个是训练图像,我们在那里应用图像增强。我们还为验证图像和测试图像创建对象。

我们开始重新调整图像的比例,使图像的像素值正常化。重新缩放参数是用于特征缩放的。在训练神经网络时,它是至关重要的。

validation_split参数允许我们将训练数据的一个子集分割成验证集。0.2意味着我们使用20%的训练集作为验证集。我们将zoom_range设置为0.2。最后,我们将horizontal_flip设置为True。

现在我们已经创建了这些对象,我们需要将它们连接到我们的数据集。ImageDataGenerator()类有一个叫做flow_from_directory的函数。该函数将图像增强工具与我们数据集中的图像连接起来。它接收到目录的路径并生成成批的增强数据。

# Awesome HyperParameters!!!
TARGET_SIZE = (200, 200)
BATCH_SIZE = 32
CLASS_MODE = 'binary'  # for two classes; categorical for over 2 classes

# Connecting the ImageDataGenerator objects to our dataset
train_generator = dgen_train.flow_from_directory(train_dir,
                                                 target_size=TARGET_SIZE,
                                                 subset='training',
                                                 batch_size=BATCH_SIZE,
                                                 class_mode=CLASS_MODE)

validation_generator = dgen_train.flow_from_directory(train_dir,
                                                      target_size=TARGET_SIZE,
                                                      subset='validation',
                                                      batch_size=BATCH_SIZE,
                                                      class_mode=CLASS_MODE)
test_generator = dgen_test.flow_from_directory(test_dir,
                                               target_size=TARGET_SIZE,
                                               batch_size=BATCH_SIZE,
                                               class_mode=CLASS_MODE)

alt text

第一个参数是数据集的路径。下一个参数是target_size。它将所有的图像调整到指定的200x200的目标尺寸。批量大小定义了我们想在每个批次中拥有多少图像。

我们使用32的批处理大小,类模式是二进制分类二进制是针对两个输出类,而分类是针对两个以上的类。因为我们只处理两个类,所以我们将类模式设置为二进制

子集参数记录了我们用于训练和验证的train_dir中的图像。我们不需要测试生成器的子集参数。只有当我们使用validation_split时,我们才使用subset参数。

注意:我们只对训练集应用图像增强。我们也可以对验证和测试集进行规范化处理。

为了得到类指数,我们使用class_indices属性。

# Get the class indices
train_generator.class_indices

alt text

我们还可以通过使用image_shape函数来获得图像的形状。

# Get the image shape
train_generator.image_shape

alt text

建立CNN模型

# Building CNN Model
model = Sequential()
model.add(Conv2D(32, (5,5), padding='same', activation='relu',
                input_shape=(200, 200, 3)))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.2))
model.add(Conv2D(64, (5,5), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.2))
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(1, activation='sigmoid'))
model.summary()

卷积层帮助我们检测图像中的特征。我们使用32个过滤器,每个大小为5x5。我们将padding设置为相同,这样我们就不会丢失图像中的信息。我们添加ReLU激活函数来引入非线性。输入的图像是200x200,有三个颜色通道的RGB。

池子大小为(2,2)的MaxPooling层将图像大小减少了一半。它有助于保持图像的特征。它还减少了参数的数量,从而缩短了训练时间。

剔除层帮助我们避免过度拟合。我们使用的比率是0.2。

我们添加了另一个有64个过滤器的卷积层和一个MaxPooling层。我们还添加了另一个Dropout层。

Flatten层将数据转换为一维矢量。密集层是我们的全连接层。我们使用256个节点的ReLU激活函数。我们再增加一个dropout层,我们用一个节点创建一个输出层,使用sigmoid激活函数。我们在输出层使用一个节点,因为它是一个二元分类问题。

alt text

编译和训练模型

# Compile the Model
model.compile(Adam(lr=0.001), loss='binary_crossentropy', metrics=['accuracy'])

我们使用亚当优化器编译我们的模型,学习率为0.001。这也是默认的学习率。

因为我们有两个类,所以我们使用的是二进制_交叉熵损失。如果我们有两个以上的类,那么我们将使用分类交叉熵。损失函数给出了衡量我们模型性能的标准。在我们的模型训练时,我们也会跟踪准确度。

# Train the Model
history = model.fit(train_generator,
          epochs=30,
          validation_data=validation_generator,
          callbacks=[
          # Stopping our training if val_accuracy doesn't improve after 20 epochs
          tf.keras.callbacks.EarlyStopping(monitor='val_accuracy', 
                                           patience=20),
          # Saving the best weights of our model in the model directory
        
          # We don't want to save just the weight, but also the model architecture
          tf.keras.callbacks.ModelCheckpoint('models/model_{val_accuracy:.3f}.h5',
                                           save_best_only=True,
                                           save_weights_only=False,
                                           monitor='val_accuracy'
                                             )
    ])

我们将我们的模型装入train_generator,并训练了30个epochs。我们将validation_data参数设置到validation_generator中。我们还设置了一些回调。随着模型的训练,我们跟踪验证的准确性。

如果验证准确率在20个历时后没有提高,我们的模型将停止训练。save_best_only参数被设置为True。它将在每个历时后保存具有最高准确度的模型。save_weights_only参数被设置为False。它不仅保存权重,而且保存模型的整个结构。

alt text

alt text

我们将表现最好的模型保存在我们之前创建的模型目录中。

alt text

我们在模型文件的名称中加入了验证精度。这样我们就很容易识别目录中的最佳模型。

我们表现最好的模型的训练损失为0.0366,训练精度为0.9857。它的验证损失为0.0601,验证精度为0.9890。

如果你没有得到一个好的验证精度,你可以增加训练的epochs数量。建议你获得更多的训练数据。你拥有的数据越多,你的模型就越准确。

训练结束后,我们得到历史对象。它包含了训练期间网络的进展。这就是我们编译模型时定义的损失和指标。

性能评估

让我们得到历史对象的键。

# Performance Evaluation
history.history.keys()

alt text

历史对象存储了每个epoch中的损失、准确性、验证损失和验证准确性的值。

我们现在绘制一张图来直观地显示每个历时训练后的训练和验证损失。

# Plot graph between training and validation loss
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.legend(['Training', 'Validation'])
plt.title('Training and Validation Losses')
plt.xlabel('epoch')

alt text

让我们也画一张图来显示每个训练历时后的训练和验证准确性。

# Plot graph between training and validation accuracy
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.legend(['Training', 'Validation'])
plt.xlabel('epoch')

alt text

让我们看看我们的模型在测试数据上的表现。记住,测试数据是模型在训练过程中没有看到的数据。

# loading the best perfoming model
model = tf.keras.models.load_model('/content/models/model_0.989.h5')

# Getting test accuracy and loss
test_loss, test_acc = model.evaluate(test_generator)
print('Test loss: {} Test Acc: {}'.format(test_loss, test_acc))

alt text

我们得到的测试损失约为0.0768,测试精度约为0.9731。

对新数据的预测

让我们用我们的模型对新数据进行预测。我们的新数据将是我们测试集中的一个随机图像。我们得到测试图像目录的路径并将其传递给*load_img()*函数。

# Making a Single Prediction
import numpy as np
from keras.preprocessing import image

# load and resize image to 200x200
test_image = image.load_img('/content/datasets/Data/test/COVID19/COVID-19 (457).jpg',
                            target_size=(200,200))

# convert image to numpy array
images = image.img_to_array(test_image)
# expand dimension of image
images = np.expand_dims(images, axis=0)
# making prediction with model
prediction = model.predict(images)
    
if prediction == 0:
  print('COVID Detected')
else:
  print('Report is Normal')

alt text

来自keras.preprocessing.image的*load_img()*函数让我们加载PIL格式的图像。第一个参数是图像的路径,第二个参数是调整我们输入图像的大小。在我们加载和调整图像大小之后,我们将其转换为一个numpy数组。

我们还必须扩大图像的尺寸。这是因为我们用图像批量训练我们的模型,每次有32张图像进入模型。所以我们在调用predict方法之前,先创建一个批次的图像。expand_dims函数的参数指定了我们要添加额外维度的位置。

批量的维度总是第一个维度,所以参数将是0。我们现在对结果进行编码,所以我们的模型将告诉我们扫描是否有Covid或正常,比如说,而不是0或1。

记住,从类的指数来看,0代表有Covid的扫描,1代表正常扫描。因此,如果预测值为0,病人有Covid。否则,病人是正常的。我们的模型在病人的X光扫描中检测到了COVID-19。

总结

在这篇文章中,我们学习了如何使用Keras建立一个图像分类器。我们应用了数据增强来增加我们数据集的大小。我们能够将我们的训练图像可视化。我们创建了一个CNN模型,并训练它对Covid-19胸部X光扫描和正常胸部X光扫描进行分类。

我们得到的测试准确率为97%,损失为0.0768。我们还能够保存我们的模型并加载它来对新数据进行预测。我们不能在现实生活中使用这个模型,因为我们没有在一个大的数据集上训练它。

现在你可以建立一个图像分类器来对图像进行分类,例如,一只狗和一只猫,一辆汽车和一辆自行车,等等。

谢谢你的阅读!!