使用多个数据集训练情感识别模型-第1部分
回到博客主页

使用多个数据集训练情感识别模型-第1部分

数据流 2023年6月15日

介绍

本教程将带您踏上使用著名的VGG模型和全面的FER数据集构建情感识别模型的旅程。我们深入研究情绪识别的基本原理,探索VGG背后的原理,并深入研究最佳电子竞技即时竞猜平台。拿来数据集。然后,我们去所有的项目组件和过程,从数据准备,模型训练,实验跟踪模型评估,装备你的技能来构建自己的情感识别模型。

参考这个存储库当我们完成这个项目时。

项目概述

在这个项目中,我们将使用VGG19在ImageNet上预训练的模型。VGG模型在ImageNet基准测试中取得了较好的性能,表明其泛化能力较好,适用于不同的视觉识别任务。它在图像识别方面非常准确,具有深度网络和小的3x3卷积滤波器。虽然ResNet等其他模型允许更深入的网络并更快地执行推理,但VGG的泛化效果更好,这在面部识别任务中很重要。

在深入研究代码之前,让我们花点时间分析一下上面显示的项目架构。

什么是FER数据集?

FER,面部表情识别,是2013年发布的开源数据集。这是由Pierre-Luc Carrier和Aaron Courville在题为“表征学习的挑战:关于三次机器学习竞赛的报告”的论文中介绍的。它保存大小为48x48像素的裁剪面部图像,以2304像素的扁平数组表示。

每张图像都使用以下映射标记为具有代表性的情绪:{0:“愤怒”,1:“厌恶”,2:“恐惧”,3:“快乐”,4:“悲伤”,5:“惊讶”,6:“中性”}

从DagsHub Repository中由dvc管理的数据集生成的可视化

数据管道概述

本项目管道由DVC管理,分为三个阶段:

  • 过程数据:提取图像特征,编码标签,分割数据集进行训练和测试,并保存到文件中。
  • 模型:利用训练数据训练模型并调优超参数。
  • 分析:使用验证数据评估模型的性能。
DVC管线架构

显示了完整的管道在这里

数据处理

进行预处理脚本涵盖了为训练准备数据的所有必要步骤。它有五个主要步骤:

  1. 将字符串像素转换为(48,48,3)图像数组。
  2. 对标签进行编码
  3. 使用分层分割将数据分成训练和验证
  4. 将训练/验证保存为numpy数据文件
  5. 将数据文件推送到我们的远程DagsHub存储库

数据首先被重塑成一个48x48 numpy二维数组,以使其与VGG模型兼容。然后数组迭代地从灰度转换为RGB。通过将灰度图像转换为RGB,我们使其与预训练的VGG模型兼容,并在所有三个通道上有效地复制灰度强度值,使模型在训练期间受益于基于颜色的特征。然后使用以下代码对相应的标签进行编码:

从keras。Utils导入to_categorical labels = df['emotion']。encoded_labels = to_categorical(labels, num_classes=7)

最后,使用分层分割将数据分成训练集和验证集。

模型开发

现在我们有了训练和验证数据,是时候构建模型、训练它并运行实验以使用模型脚本。下图显示了一个标准的VGG19模型,以及重新编排模型的最后一个块后显示的模型。

我在DagsHub上发现了一件很酷的事情,它使用Nitron来可视化模型的架构!你可以去看看在这里

为了在对新数据集进行训练之前保持模型的权重,我们将FER数据集的最后一个卷积块替换为输出层,将MaxPooling2D替换为GlobalAveragePooling2D,并为模型输出添加最终的密集层。

Base_model = tf.keras.applications。VGG19(weights='imagenet', include_top=False, input_shape=(48, 48, 3)) # Add dense layers x = base_model.layers[-2].output x = GlobalAveragePooling2D()(x) # Add final classification layer output_layer = Dense(num_classes, activation='softmax')(x) # Create model model = Model(inputs=base_model.input, outputs=output_layer)

GlobalAveragePooling2D层通过计算每个特征映射的平均值来减少计算时间,这迫使网络学习与任务全局相关的特征,从而产生具有较少数量值的一维向量。虽然这是模型的一般体系结构,但它还没有准备好进行部署。

数据增加

在数据增强方面,在前一阶段,我在数据集中存储了每个类标签的计数。你可以看到这是一个不平衡的数据集;与厌恶和惊讶相比,关于快乐、中立和悲伤的数据点要多得多。

{“3”:8989年,“6”:6198年,“4”:6077年,“2”:5121年,“0”:4953年,“5”:4002年,“1”:547}情感标签→{0:‘愤怒’,1:“厌恶”,2:“恐惧”,3:‘幸福’,4:“悲伤”,5:“惊喜”,6:“中性”}

有两种方法可以处理不平衡的数据集。第一种方法是使用ImageDataGenerator生成更多的数据。这通常用于深度学习任务,通过随机旋转、平移、剪切、缩放、翻转和其他图像修改器来生成更多的训练样本。应用该技术将有助于模型更好地泛化,并提高测试集上的准确性,因为该模型偏向于某些标签。实现如下所示。数据生成器被拟合到训练集上,并在训练时被纳入模型。

train_datagen = ImageDataGenerator(rotation_range=20, width_shift_range=0.20, height_shift_range=0.20, shear_range=0.15, zoom_range=0.15, horizontal_flip=True, fill_mode='nearest') train_datagen.fit(X_train)

处理不平衡数据集的第二种方法是在模型训练时传递类权重。得到的权重可以用来平衡训练过程中的损失函数。类权重会根据输入数据的频率自动调整为:N_samples / (n_classes * np.bincount(y))

class_weights = compute_class_weight(class_weight = "balanced", classes = np.unique(y_train.argmax(axis=1)), y = y_train.argmax(axis=1)) class_weights_dict = dict(enumerate(class_weights)) ##现在训练模型历史= model.fit(train_datagen. cn)。flow(X_train, y_train, batch_size = batch_size), validation_data = (X_valid, y_valid), stepps_per_epoch = stepps_per_epoch, epochs = epochs, callbacks = callbacks, use_multiprocessing = True, class_weight=class_weights_dict)

class_weights_dict是随着train_datagen生成随机批次的数据,平衡损失函数。

回调,提前停止和学习率调度器,也可以通过降低学习率来处理过拟合,如果损失在epoch中没有减少或完全停止训练。

lr_scheduler = ReduceLROnPlateau(monitor = 'val_accuracy', factor = 0.25, patience = 8, min_lr = 1e-6, verbose = 1) early_stop = earlystop (monitor = 'val_accuracy', min_delta = 0.00005, patience = 12, verbose = 1, restore_best_weights = True)

耐心参数表示在回调应用于训练过程之前需要发生多少次连续性能下降。

我们可以比较两个实验使用匹配的参数运行。模型之间唯一的变化是在训练过程中纳入了类权重。正如您所看到的,添加了这个功能后,模型的性能稍微好一些。的train_loss显着降低,验证精度也略有提高。

调整模型的下一步是找到最适合的超参数。实现这一点的一种方法是使用Keras调优器。Keras调优器是一个内置模块,可以通过搜索提供的一组特定超参数来将其应用于Keras模型。

我做了两个不同的实验。第一个测试侧重于测试不同的优化器集、学习率、批处理大小和时间。我们将使用tune_model.ipynb来做这些实验。

参数作为hp对象传递。给它们一个最小值和最大值,以及一个阶跃值,以提供一个可以测试的值范围。在搜索的第一次迭代中,我们想要测试批大小、epoch和学习率的不同组合。

从kerastuner.engine. HyperParameters导入HyperParameters。tuners import RandomSearch hp = HyperParameters() batch_size = hp。Int('batch_size', min_value=16, max_value=256, step=16) epochs = hp。Int('epochs', min_value=10, max_value=50, step=10)

hp对象被传递到构建模型函数中,并在每次试验中测试一组不同的值。试验次数设置为最大试验次数。根据超参数的数量、步长和总体范围,增加或减少试验次数可能是有意义的,以便调谐器能够有效地搜索这些参数的足够组合。目标是最大化验证的准确性,如下所示。

tuner = RandomSearch(build_model, objective='val_accuracy', max_trials=10, hyperparameters=hp) tuner.search(train_datagen. cn)flow(X_train, y_train, batch_size=batch_size), validation_data=(X_valid, y_valid), epochs=epochs, callbacks=[early_stop, lr_scheduler, mlflow_callback], use_multiprocessing=True)

MLFlow回调

我们还没有讨论的一个回调是mlflow_callback。回调在每个epoch结束时跟踪模型精度的变化,并将其作为实验记录到DagsHub。为了将其合并到keras调优器中,我创建了一个MLflow类来跟踪试验中每个epoch之后验证精度的变化。

类MlflowCallback(Callback): def __init__(self, run_name): self.run_name = run_name def on_train_begin(self, logs=None): mlflow.set_tracking_uri("") mlflow.start_run(run_name=self.run_name) def on_trial_end(self, trial, logs={}): hp = trial.hyperparameters.values for key, value in hp.items():mlflow.log_param(key, value) mlflow.log_param('epochs', 32) mlflow.log_param('batch_size', 212) mlflow.log_param('learning_rate', 0.003) mlflow.log_metric("val_accuracy", logs["train_accuracy"]) mlflow.log_metric("val_loss", logs["val_accuracy"]) mlflow.log_metric("val_loss", logs["val_loss"]) mlflow.log_metric("train_loss", logs["val_loss"]) def on_train_end(self, logs=None): mlflow.end_run() mlflow_callback = MlflowCallback('layers add')

mlflow回调在keras调优器开始搜索时设置跟踪uri。您需要进行配置mlflow用你自己的证件。在设置跟踪uri之前,您必须将跟踪用户名和密码导出为项目shell中的环境变量,如下所示。

export MLFLOW_TRACKING_USERNAME=<您的用户名> export MLFLOW_TRACKING_PASSWORD=<您的密码/令牌>

当试验完成时,记录的度量标准和超参数将存储在DagsHub存储库中的实验选项卡中。还生成了一个图来显示每个历元的精度变化。

跟踪验证精度在不同时期的样本试验结果

我发现SGD优化器在低学习率、大批大小和epoch范围从30到40的情况下工作得最好。这是实验这产生了这组参数的最佳结果。

下一组搜索的参数是额外的密集层、这些层内的单元数量和正则化惩罚。第一次搜索的最佳参数被传递到接下来的试验中。

现在我想测试添加额外的层和添加正则化是否有助于改善过拟合或提高精度hp = HyperParameters() num_dense_layers = hp。Int('num_dense_layers', min_value=1, max_value=3) num_units = hp。Int('num_units', min_value=64, max_value=512, step=32) reg_strength = hp。Float('reg_strength', min_value=0.001, max_value= 0.1, step=0.004)flow(X_train, y_train, batch_size=212), validation_data=(X_valid, y_valid), epochs = 32, steps_per_epoch = len(X_train) / 212, callbacks=[early_stopped, lr_scheduler, mlflow_callback], use_multiprocessing=True) best_hp = tuner.get_best_hyperparameters()[0] mlflow.log(best_hp.values)

我们添加额外的层并应用正则化的原因是看看更高的学习率和epoch计数是否有效。这样做的缺点是它可能导致过拟合,这在一些实验中确实发生过。然而,增加额外的密集层和L2正则化可以减轻过拟合。

该模型似乎对增加的图层反应不佳,并且在训练过程中准确性趋于稳定。

样本试验结果跟踪验证精度的时代

实验跟踪

一旦找到了一组性能良好的参数,就不再需要使用keras调优器了。我们可以再次使用MLFlow训练模型并记录参数。设置跟踪uri并开始运行之后,我们将按照前面所示构建和训练模型。现在我们可以定义所有参数,并在运行结束时记录结果。

mlflow.set_tracking_uri("")与mlflow.start_run(): #批大小为32执行最佳。model = build_model(num_classes) history = train(model, X_train, y_train, X_valid, y_valid) metrics = {"train_accuracy":历史。History ['accuracy'][-1], "val_accuracy":历史。History ['val_accuracy'][-1], "train_loss":历史记录。History ['loss'][-1], "val_loss":历史记录。history['val_loss'][-1]} params = {"optimizer": {'type': 'sgd', 'learning_rate': 0.0092, 'momentum': 0.90, 'nesterov': True}, "loss": 'categorical_crossentropy', 'batch_size': 96, 'epochs': 24, 'callbacks': [' earlystop ', 'ReduceLROnPlateau'], 'data_augmentation': 'ImageDataGenerator'} mlflow.log_metrics(metrics) mlflow.log_params(params)

模型评价

在多次实验后,我比较了前3名的表现。我想选择一种能够最大化验证精度同时最小化损失的模型。我还比较了训练精度和损失,以确保模型没有过度拟合。第二种模式表现最好。我保存了这个模型在我的存储库中。

建模过程的最后一步是评估模型的性能。我们利用造纸厂执行评价笔记本在一个单独的eval脚本,以便它可以由DVC管道处理。

让我们看看模型在验证数据集上的性能结果。

精度 回忆 f1-score 支持
愤怒 0.56 0.64 0.60 743
厌恶 0.58 0.49 0.53 82
恐惧 0.54 0.44 0.49 768
幸福 0.89 0.87 0.88 1349
悲伤 0.58 0.57 0.57 912
惊喜 0.67 0.81 0.74 600
中性 0.65 0.62 0.63 930
精度 0.67 5384
宏avg 0.64 0.63 0.63 5384
加权平均 0.67 0.67 0.67 5384

错误验证预测总数:1773

见解和结论

考虑到我们使用的是一个不平衡的数据集,由于测试中提供的样本不足,该模型很难准确地预测厌恶、恐惧和悲伤标签。缺乏特定类标签的训练样本是许多多类分类器的主要问题。此外,即使加入回调和正则化,模型也会过拟合。这可能是因为我们正在使用VGG模型中的每个可训练参数来调整数据集,而不是潜在地冻结某些层。由于模型中参数的复杂性和数量,它可能难以推广到新数据。

在不同的数据集上训练这个模型可能会有用,以提高它对新数据的泛化能力,减少对某些类标签的偏差,并提高模型的整体性能。

我们可以通过在各自的存储库中托管不同的数据集并利用DagsHub客户使用迁移学习的概念,以一种有效的方式在多个数据集上对数据流和模型进行训练。请继续关注下一篇文章,了解这是如何实现的。


额外的资源

如果您对直接从远程存储库流式传输数据感兴趣,可以使用主要分支这个项目的。主分支利用DagsHub的直接数据访问(Direct Data Access),这是一个连接到DagsHub中的远程存储库以传输数据的API。脚本执行相同的逻辑,但是,它不利用DVC管道将文件存储为依赖项和接收文件对象。相反,它利用Python Hooks对已经在远程存储库中的数据集进行流处理。请参阅存储库的README,了解如何修改项目以直接从远程存储库传输数据。

如果你有任何问题,请随时联系我。你可以加入我们的不和在美国,我们建立了一个充满活力、乐于助人、友好的社区。

标签

Gaurav汉

我是一名数据科学专业人士,喜欢通过应用用例探索和撰写关于新的AI/ML机制的博客。

太棒了!您已成功订阅。
太棒了!接下来,完成签出以获得完全访问权限。
欢迎回来!您已成功登录。
成功!您的帐户已完全激活,您现在可以访问所有内容。