这可能是拉开开源融合党和kaggle老鸟的一个非常关键的地方,也是很多排行榜劝退新手的地方,也是大家用来闷声发大财而闭口不谈的小技巧。
上一次这种情况发生在kdd2019,因为在知乎上透露了一点指标优化,不少队伍受启发突飞猛进。而在这个比赛中,指标优化的差距比lstm和bert之间的差距都大。那我们就从这个比赛出发,看看一些常见的比赛中可以用到的指标优化trick。
多分类问题下面,我们经常会遇到一些指标,比如正在进行的DF平台上的比赛,疫情期间网民情绪识别,用这个trick就能轻易地从0.726提升到0.738。
简单地说就是如果多类别不均衡的话,这时候直接使用神经网络优化交叉熵损失得到的结果,f1显然不是全局最优的,很多同学都会问,二分类下的阈值搜索我会,如果是多分类怎么做一个阈值搜索呢?传统的多分类我们预测结果使用argmax(logits)这时候,可以形式化的表达为求argmax(w*logits)使得f1均值最大。其中w就是要求得的再放缩权重。 我们可以使用非线性优化的方法,求解这个问题,scipy的库里有很多实现了。
再比如正在进行的Kaggle平台上的Bengali手写体识别比赛,使用这个技巧在线上可以有一定的提升,其指标是macro_recall。
我们经常遇到这样的问题,比如情感打分预测1-5,我们用mse指标来评价,通常,我们用回归拟合1-5的时候,如何切分阈值对我们的结果有很大的影响,在这里我们也是进行阈值的搜索,不一样的是。我们的阈值要在1-5之间。使用的方法也是非线性优化。下面一段代码给一个简单的例子,同理前面多分类下的准召优化我们也可以这样写
from functools import partial
import numpy as np
import scipy as sp
class OptimizedRounder(object):
def __init__(self):
self.coef_ = 0
def _kappa_loss(self, coef, X, y):
X_p = np.copy(X)
for i, pred in enumerate(X_p):
if pred < coef[0]:
X_p[i] = 0
elif pred >= coef[0] and pred < coef[1]:
X_p[i] = 1
elif pred >= coef[1] and pred < coef[2]:
X_p[i] = 2
elif pred >= coef[2] and pred < coef[3]:
X_p[i] = 3
else:
X_p[i] = 4
ll = quadratic_weighted_kappa(y, X_p)
return -ll
def fit(self, X, y):
loss_partial = partial(self._kappa_loss, X=X, y=y)
initial_coef = [0.5, 1.5, 2.5, 3.5]
self.coef_ = sp.optimize.minimize(loss_partial, initial_coef, method='nelder-mead')
def predict(self, X, coef):
X_p = np.copy(X)
for i, pred in enumerate(X_p):
if pred < coef[0]:
X_p[i] = 0
elif pred >= coef[0] and pred < coef[1]:
X_p[i] = 1
elif pred >= coef[1] and pred < coef[2]:
X_p[i] = 2
elif pred >= coef[2] and pred < coef[3]:
X_p[i] = 3
else:
X_p[i] = 4
return X_p
def coefficients(self):
return self.coef_['x']
Google QUEST Q&A Labeling这个比赛是多标签(multi-lable)分类比赛,指标是spearman相关系数,特点是衡量两个结果的排序一致性。数据集的标注几个人标注后取平均,带来的结果就是,多标签的每个类上打分是nunique < 10的离散值,划重点,注意这里标注的是离散值。而spearman的特点是:
a = np.array([0.5, 0.5, 0.7, 0.7])
b = np.array([4., 5., 6., 7.])
print_spearmanr(a, b) # --> 0.89
b2 = np.array([4., 4., 6., 6.])
print_spearmanr(a, b2) # --> 1.
我们的神经网络,预测的logits 即使到fp16也很难一模一样,所以我们预测的结果一般nunique==len(predict),而标注nunique可是个位数。那么问题来了,标注的数量是有限个,你如果你不对标注的结果进行分箱离散化。那么就吃了大亏了...,所以你请你使用我们上述讲过的方法,设计一个好的办法来优化这件事情吧。