最近一直在做多任务,但是效果好象没什么提升,因为都是凭自己的想法和感觉在做。于是上网查找了一些这方面的资料,寻求一些理论上的支撑和前人经验上的帮助。
多任务学习:
故名思意,就是多个任务一起学习。为什么要进行多任务学习呢?因为现实中样本采样的成本较高,而训练样本不足常常会出现过拟合的现象,而将多个相关任务同时学习,通过共享某个共同的知识可以提高各任务的泛化效果
。
分类:
基于软共享的深度多任务学习
基于硬共享的深度多任务学习
一些问题:
1、损失的整合
为多个任务定义一个损失函数,若将每个任务的损失进行简单相加,由于不同任务的收敛速度不同,可能某一任务的收敛得到不错的效果,而其他任务表现却很差。
简单的解决办法是将简单相加变为加权相加,但这样会不时进行调参。
论文《Multi-Task Learning Using Uncertainty to Weigh Losses for Scene Geometry and Semantics》,提出引入不确定性来确定损失的权重:在每个任务的损失函数中学习另一个噪声参数(noise parameter)。此方法可以接受多任务(可以是回归和分类),并统一所有损失的尺度。这样就能像一开始那样,直接相加得到总损失了。该方法不仅可以得到很好的结果而且不需要考虑额外的权重超参数。
2、调节学习速率
学习速率是最重要的超参数之一。我们发现,任务 A 和任务 B 各自合适的速率可能是不同的。这时,我们可以在各个任务的子网络(基于硬共享的深度多任务学习)分别调节各自的学习速率,而在共享网络部分,使用另一个学习速率。
虽然听上去很复杂,但其实非常简单。通常,在利用 TensorFlow 训练神经网络时,使用的是:
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)
AdamOptimizer 定义如何应用梯度,而 minimize 则完成具体的计算和应用。我们可以将 minimize 替换为我们自己的实现方案,在应用梯度时,为计算图中的各变量使用各自适合的学习速率。
all_variables = shared_vars + a_vars + b_vars
all_gradients = tf.gradients(loss, all_variables)
shared_subnet_gradients = all_gradients[:len(shared_vars)]
a_gradients = all_gradients[len(shared_vars):len(shared_vars + a_vars)]
b_gradients = all_gradients[len(shared_vars + a_vars):]
shared_subnet_optimizer = tf.train.AdamOptimizer(shared_learning_rate)
a_optimizer = tf.train.AdamOptimizer(a_learning_rate)
b_optimizer = tf.train.AdamOptimizer(b_learning_rate)
train_shared_op = shared_subnet_optimizer.apply_gradients(zip(shared_subnet_gradients, shared_vars))
train_a_op = a_optimizer.apply_gradients(zip(a_gradients, a_vars))
train_b_op = b_optimizer.apply_gradients(zip(b_gradients, b_vars))
train_op = tf.group(train_shared_op, train_a_op, train_b_op)
注:这个技巧其实在单任务网络中也很实用
3、将估计作为特征
当完成第一阶段的工作,为预测多任务创建好神经网络后,我们可能希望将某一个任务得到的估计(estimate)作为另一个任务的特征。在前向传递(forward-pass)中,这非常简单。但在反向传播中呢?
假设将任务 A 的估计作为特征输入给 B,我们可能并不希望将梯度从任务 B 传回任务 A,因为我们已经有了任务 A 的标签。对此,TensorFlow 的 API 所提供的 tf.stop_gradient 会有所帮助。在计算梯度时,它允许你传入一个希望作为常数的张量列表,这正是我们所需要的。
all_gradients = tf.gradients(loss, all_variables, stop_gradients=stop_tensors)
不止如此,该技术可用在任何你希望利用 TensorFlow 计算某个值并将其作为常数的场景。
我的一些想法:
关于多个任务的训练,应该也可以不统一成一个损失函数,各个任务拥有自己的损失函数即可。
这样可以分别找到适合各个任务的学习速率,和迭代次数,然后进行次数不同迭代即可。
比如:
任务A需要迭代100次才收敛:optimizer1 = tf.train.AdamOptimizer(learning_rate1).minimize(loss1)
任务B需要迭代10次收敛:optimizer2 = tf.train.AdamOptimizer(learning_rate2).minimize(loss2)
# 训练:
for epoch in range(100):
# Task A
sess.run([optimizer1], feed_dict1)
# Task B
if epoch % 10 == 0:
sess.run([optimizer2], feed_dict2)
【当然这部分只是我的想法啦!没什么科学依据】