65.9K
CodeProject 正在变化。 阅读更多。
Home

深度学习年龄估计:训练 CNN

2020年7月23日

CPOL

6分钟阅读

viewsIcon

9639

downloadIcon

124

在本文中,我们将训练CNN进行年龄估计。

在本系列文章中,我们将向您展示如何使用深度神经网络 (DNN) 从图像中估计一个人的年龄。

在设计并构建了用于年龄估计的CNN模型之后,在本文——本系列文章的第五篇——中,我们将训练该模型将图像中的人物分类到适当的年龄组。

我们需要实现以下功能

  • 预处理图像以满足网络的输入标准
  • 将图像从文件加载到内存
  • 将数据转换为模型优化可接受的格式
  • 启动训练过程

准备用作输入的图像

我们设计的CNN期望输入数据由128x128像素的灰度(单通道,8位)图像组成。现在我们需要提供一些转换功能来将原始(彩色)图像预处理成有效的输入格式。以下是定义两个类来实现转换功能的Python代码:

import cv2
class ResizeConverter:
    def __init__(self, width, height):
        self.width = width
        self.height = height
 
    def convert(self, image):
        return cv2.resize(image, (self.width, self.height), cv2.INTER_AREA)
	
class GrayConverter:
    def convert(self, image):
        return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

ResizeConverter类用于将图像调整到指定的宽度和高度。请注意,我们首先导入了OpenCV包cv2。该包包含我们处理图像数据所需的所有函数。该类的resize方法使用带有指定参数值的cv2.resizecv2.INTER_AREA插值类型是图像缩小的推荐算法。

GrayConverter类只有一个方法;它将彩色图像转换为8位单通道灰度格式,由cv2.COLOR_BGR2GRAY值指定。

定义图像加载过程

实现了转换器类后,我们现在可以实现用于将图像加载到内存的数据集类了。

import os
import numpy as np
import cv2

class ImageDataset:
    def __init__(self, converters):
        self.converters = converters
    
    def get_files(self, folder):
        filenames = os.listdir(folder)
        for filename in filenames:
            filepath = os.path.join(folder, filename)
        	yield filepath
    
    def load(self, folder):
        self.images = []
        self.labels = []
        files = list(self.get_files(folder))
        for (i, path) in enumerate(files):
            image = cv2.imread(path)
            fname = os.path.basename(path)
            label = fname.split('_')[0]
            if self.converters is not None:
                for c in self.converters:
                    image = c.convert(image)
        	self.images.append(image)
        	self.labels.append(int(label))
        	
    def get_data(self):
        return (np.array(self.images), np.array(self.labels))

类构造函数接收一个参数——一组用于图像预处理的转换器。主方法load需要一个参数——包含图像文件的文件夹的完整路径。此方法查找目录中的所有文件,使用cv2.imread函数从每个文件读取图像,然后将所有转换器应用于该图像。它还会从文件名中解析年龄标签,并将它们存储为整数值。有关文件命名语法的描述,请参阅本系列的第二篇文章。

将数据转换为可优化格式

CNN模型训练前的最后一步是将加载的图像转换为特殊格式。这通过AgeClassConverter类的convert方法来实现。

import numpy as np
from keras.preprocessing.image import img_to_array
from sklearn.preprocessing import LabelBinarizer
 
class AgeClassConverter:
    @staticmethod
    def convert(imdataset, ageranges):
        (images, labels) = imdataset.get_data()
        arrays = []
        for (i, image) in enumerate(images):
            arr = img_to_array(image, data_format="channels_last")
            arrays.append(arr)
        arrays = np.array(arrays).astype("float")/255.0 
        
        k = len(ageranges)
        for (i, label) in enumerate(labels):
            for (j, r) in enumerate(ageranges):
                if j<(k-1) and label>=ageranges[j] and label<ageranges[j+1]:
                    labels[i] = j+1
                    break
        
        lb = LabelBinarizer()
        lb.fit(range(1, k));
        binlabels = np.array(lb.transform(labels))
        
        return (arrays, binlabels)

convert方法的第一个参数是ImageDataset类的一个实例。第二个参数是用于形成年龄组范围的年龄值列表。方法中的第一个循环遍历数据集中的图像,并使用img_to_array函数将每个图像转换为特殊的Keras数组格式。请注意,我们将数据格式指定为channels_last。假设图像的通道遵循空间维度——宽度和高度。循环结束后,我们将数据归一化到[0, 1.0]范围,将值除以255.0。

该方法的第二个循环将标签中的整数年龄值转换为年龄组。例如,假设我们使用以下年龄范围值[1, 6, 11, 16, 19, 22, 31, 45, 61, 81, 101]调用该方法。有十一个值,提供十个年龄间隔:1-5, 6-10, 11-15, ..., 81-100。如果数据集包含五个年龄值为[2, 6, 8, 15, 21]的标签,则该循环会将这些值转换为组指示符[1, 2, 2, 3, 5]。

循环结束后,我们使用从sklearn.preprocessing包导入的LabelBinarizer类,将标签值转换为用于分类问题的特殊二进制格式。该格式不是为标签(年龄组)提供一个单一值,而是为所有可能的类提供概率值。例如,转换我们上面示例中的五个标签值将产生以下二值化数据:

[ [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 1, 0, 0, 0, 0, 0] ]

如您所见,二值化数据包含所有年龄组的概率数组。由于我们有十个年龄组,每个概率数组有十个值:如果年龄不在范围内,则值为零,如果年龄标签属于该组,则值为单位。

加载图像

现在我们有了所有类别的代码,可以将数据集加载到内存中。

ageranges = [1, 6, 11, 16, 19, 22, 31, 45, 61, 81, 101]
classes = len(ageranges)-1
imgsize = 128
rc = ResizeConverter(imgsize, imgsize)
gc = GrayConverter()
 
trainSet = ImageDataset([rc, gc])
trainSet.load(r"C:\Faces\Training")
(trainData, trainLabels) = AgeClassConverter.convert(trainSet, ageranges)
 
testSet = ImageDataset([rc, gc])
testSet.load(r"C:\Faces\Testing")
(testData, testLabels) = AgeClassConverter.convert(testSet, ageranges)

在上面的代码中,我们为年龄范围列表和图像大小赋值。然后,我们实例化用于图像缩放和颜色转换的转换器。转换器列表用作构建训练集和测试集的参数。数据集通过load方法从磁盘加载,其中包含图像文件目录的路径。最后,我们调用静态AgeClassConverter.convert方法将我们的数据集转换为Keras优化算法可接受的格式。

训练模型

我们现在准备启动训练过程。

frep = net.fit(trainData, trainLabels, validation_data=(testData, testLabels), batch_size=128, epochs=20, verbose=1)
netname = r"C:\Faces\age_class_net_"+str(kernels)+"_"+str(hidden)+".cnn"
net.save(netname)

net表示我们的CNN模型,我们已经在几篇文章前实例化了它。我们调用其fit方法来启动优化过程。该方法的参数是:

  • trainDatatrainLabels分别是训练数据和二值化标签。
  • validation_data是测试数据和标签的元组。
  • batch_size是所选SGD优化方法的批处理大小。
  • epochs是训练过程的迭代次数(对整个数据集的迭代)。
  • verbose是在过程中显示的信息级别。

执行代码将启动模型训练过程。请注意,该过程可能需要数小时才能在平均CPU上完成。在执行过程中,输出中会显示有关过程迭代的信息。它看起来像这样:

Train on 21318 samples, validate on 2369 samples
Epoch 1/20
21318/21318 [==============================] - 1146s 54ms/step - loss: 1.7091 - accuracy: 0.4166 - val_loss: 2.2536 - val_accuracy: 0.0912
Epoch 2/20
21318/21318 [==============================] - 1124s 53ms/step - loss: 1.3156 - accuracy: 0.5058 - val_loss: 1.6474 - val_accuracy: 0.4116
Epoch 3/20
21318/21318 [==============================] - 1118s 52ms/step - loss: 1.2010 - accuracy: 0.5439 - val_loss: 1.2562 - val_accuracy: 0.5230

有两个值需要您注意:accuracyval_accuracy。前者是训练集上分类的精度,后者是测试集上年龄组预测的精度。正如您在上面的示例输出中所见,最后一个val_accuracy值为0.5222。这意味着在此步骤中,我们的CNN正确预测了测试集中52%图像的年龄组。我们应该跟踪这些值,以确保优化过程收敛。理想情况下,两个值都会单调增加到1.0。

指定迭代次数后,过程将停止,CNN模型将保存到磁盘。在我们示例中达到的最终测试精度约为56%。预测精度可以通过各种方法提高,例如使用更大的数据集和更深的神经网络架构、正则化、数据增强等。

下一步

现在我们有了预训练好的CNN保存在磁盘上。下一步是使用它从图像中进行年龄估计。

© . All rights reserved.