未经同意,不得转载
本文以复现论文《Fine-Grained Head Pose Estimation Without Keypoints》为例说明如何使用Tensorflow & Keras自定义 multi-loss 函数。
论文地址:
https://arxiv.org/abs/1710.00925
复现代码:
https://github.com/Oreobird/tf-keras-deep-head-pose
一、原理简述
论文提出了一种无需人脸关键点的人脸头部姿态估计算法,通过训练一个multi-loss的卷积神经网络,该网络结合了分类和回归两种目标函数来预测3个人脸头部姿态角度(yaw,pitch 和 roll),网络结构比较简单,以ResNet50作为主干网络提取特征,然后分别对3个角度进行分类和回归,属于多输出模型,结构如下图所示:
二、计算细节
(1)角度的分类定义
角度属于连续值,怎么转换为分类问题呢?作者使用了分箱的思想,将连续的角度值在[-99, 99]的范围内以 3 为间隔划分为66个区间,也就是66个类,如下图的例子,类别标签从0开始,-94度落在了第 1 类的范围内,所以分为第1类。
(2)回归损失的计算
分类得出的softmax是分类的概率结果,作者先对类别求了个期望值,即用softmax的值乘以各对应的类别标签再相加,再乘以3 - 99来恢复成连续的角度值,最后才和实际的角度计算MSE损失。这个过程从作者的代码可以看出:
idx_tensor = [idx for idx in xrange(66)]
yaw_predicted = torch.sum(yaw_predicted * idx_tensor, 1)
(3) loss最终式
将MSE-loss前面乘以一个权重系数α,再与分类loss加权得到最终的multi-loss。
三、Tensorflow & Keras 复现
复现比较简单,关键就是multi-loss函数的计算过程,理清楚作者的思路后基于Keras可以很容易写出整个过程。
(1)自定义mult-loss函数
def __loss_angle(self, y_true, y_pred, alpha=0.5):
bin_true = y_true[:,0] #离散值
cont_true = y_true[:,1] #连续值
# cross entropy loss
cls_loss = tf.losses.softmax_cross_entropy(onehot_labels=tf.keras.utils.to_categorical(bin_true, 66), logits=y_pred)
# MSE loss
pred_cont = tf.reduce_sum(tf.nn.softmax(y_pred) * self.idx_tensor, 1) * 3 - 99
mse_loss = tf.losses.mean_squared_error(labels=cont_true, predictions=pred_cont)
# Total loss
total_loss = cls_loss + alpha * mse_loss
return total_loss
其中的y_true传进来的是[bin_y, cont_y],即离散的角度值和连续的角度值,具体可以看datasets.py的data_generator()函数。
(2)模型构建
有3个角度需要计算loss,所以是多输出模型:
def __create_model(self):
inputs = tf.keras.layers.Input(shape=(self.input_size, self.input_size, 3))
feature = tf.keras.layers.Conv2D(filters=64, kernel_size=(11, 11), strides=4, padding='same', activation=tf.nn.relu)(inputs)
feature = tf.keras.layers.MaxPool2D(pool_size=(3, 3), strides=2)(feature)
feature = tf.keras.layers.Conv2D(filters=192, kernel_size=(5, 5), padding='same', activation=tf.nn.relu)(feature)
feature = tf.keras.layers.MaxPool2D(pool_size=(3, 3), strides=2)(feature)
feature = tf.keras.layers.Conv2D(filters=384, kernel_size=(3, 3), padding='same', activation=tf.nn.relu)(feature)
feature = tf.keras.layers.Conv2D(filters=256, kernel_size=(3, 3), padding='same', activation=tf.nn.relu)(feature)
feature = tf.keras.layers.Conv2D(filters=256, kernel_size=(3, 3), padding='same', activation=tf.nn.relu)(feature)
feature = tf.keras.layers.MaxPool2D(pool_size=(3, 3), strides=2)(feature)
feature = tf.keras.layers.Flatten()(feature)
feature = tf.keras.layers.Dropout(0.5)(feature)
feature = tf.keras.layers.Dense(units=4096, activation=tf.nn.relu)(feature)
fc_yaw = tf.keras.layers.Dense(name='yaw', units=self.class_num)(feature)
fc_pitch = tf.keras.layers.Dense(name='pitch', units=self.class_num)(feature)
fc_roll = tf.keras.layers.Dense(name='roll', units=self.class_num)(feature)
model = tf.keras.Model(inputs=inputs, outputs=[fc_yaw, fc_pitch, fc_roll])
losses = {'yaw':self.__loss_angle,
'pitch':self.__loss_angle,
'roll':self.__loss_angle}
model.compile(optimizer=tf.train.AdamOptimizer(), loss=losses)
return model
四、总结
(1)论文的思路巧妙在于把回归的问题转化为分类+回归的问题,利用分类的结果来引导回归。
(2)Keras实现这种multi-loss的任务,除了自定义损失函数的方式外,还可以自定义层,比如使用简单的Lambda层来实现,此为后话。
网友评论