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





5.00/5 (3投票s)
在本文中,我们将训练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.resize
。cv2.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
方法来启动优化过程。该方法的参数是:
trainData
和trainLabels
分别是训练数据和二值化标签。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
有两个值需要您注意:accuracy
和val_accuracy
。前者是训练集上分类的精度,后者是测试集上年龄组预测的精度。正如您在上面的示例输出中所见,最后一个val_accuracy
值为0.5222。这意味着在此步骤中,我们的CNN正确预测了测试集中52%图像的年龄组。我们应该跟踪这些值,以确保优化过程收敛。理想情况下,两个值都会单调增加到1.0。
指定迭代次数后,过程将停止,CNN模型将保存到磁盘。在我们示例中达到的最终测试精度约为56%。预测精度可以通过各种方法提高,例如使用更大的数据集和更深的神经网络架构、正则化、数据增强等。
下一步
现在我们有了预训练好的CNN保存在磁盘上。下一步是使用它从图像中进行年龄估计。