背景:nlp模型,基于bert预训练模型做的fine-tuning,用来多文本的多标签任务,一个文本会有多个标签。
预习知识:
1.分类层后面添加sigmod函数:
经过sigmod+互信息输出的结果是伯努利分布:p(y1|x) p(y2|x)
softmax输出的结果是多项分布 p(y1,y2…|x)
对于贰分两类问题:
softmax输出的两个值,相加为1。比如(0.3,0.7)表示是的概率为0.7,否的概率为0.3
对于sigmod来说,也得到2个值,不过没有可加性,两个值均为0到1的某个数,对于一个值p来说,1-p是它对应的另外一个概率。比如(0.4,0.8),sigmod认为输出第一位的概率为0.4,第一位不为1的概率为0.6,输出为第二位的概率为0.8,不为第二位的概率为0.2.
分类问题:类别之间是互斥的。
多标签问题:类别之间不要求互斥,是否属于当前类别,跟别的类别没有关系。
多分类用softmax:归一化指数函数
因为多分类最终只能属于一个类别,所以要将分类层的输出用softmax得出每个类的相对概率。
多标签用sigmod:
一个样本可能属于多个类别,只能得出样本是不是某个类别的概率,跟它是不是其它类别没关系。
1.多标签模型的损失函数介绍:
1. BCELoss
该类主要用来创建衡量目标和输出之间的二进制交叉熵的标准。用法如下:
torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
参数:
- weight,表示对loss中每个元素的加权权值;
- reduction, 指定输出的格式,包括'none','mean','sum';
- 其它两个参数一般不推荐使用;
形状:
- input,(N, *);
- target,(N, *);
- output,标量,当reduction为'none'时,为(N, *)。
计算
当reduction参数为'none',即对计算结果不进行处理时,loss可以表示为,
其中,
这里N为batch size。如果reduction参数不为'none'时,
这里需要注意的是target的值必须为0或1。
2. BCEWithLogitsLoss
这个loss类将sigmoid操作和与BCELoss集合到了一个类。
用法如下:
torch.nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)
参数:
- weight (Tensor),针对每个loss元素的加权权值;
- reduction (string), 指定输出的格式,包括'none','mean','sum';
- pos_weight (Tensor),正样例的权重,必须为长度为类别数量的向量,主要可用来处理类别不均衡情形。
- 其它两个参数一般不推荐使用;
形状:
- input,(N, *);
- target,(N, *);
- output,标量,当reduction为'none'时,为(N, *)。
计算过程与BCELoss类似,除了增加一个sigmoid层,
3. binary_cross_entropy_with_logits
该函数主要度量目标和输出之间的二进制交叉熵。与第2节的类功能基本相同。
用法如下:
torch.nn.functional.binary_cross_entropy_with_logits(input, target, weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)
其参数与BCEWithLogitsLoss基本相同。
2.如何确定模型的预测结果
先看一个模型预测的结果,有三个label:
[0.8806893229484558, 0.03691115230321884, 0.03341011703014374]
这个list的索引就是对应的标签label,是第0个标签的概率0.8806893229484558,同理以此类推。是否为某一个标签的概率不完全一致,这个概率跟各个标签样本比例有关系,样本占比大的标签,是这个标签的阈值大一些。曾经简单统计过训练样本的占比跟测试集预测得分的均值情况,二者走势大致一致。至于凸起点,通过观察测试样本,这些标签类别较为容易识别,或者说这个标签跟其他标签相比,有自己独特的属性,可区别于其他标签,所以这些标签虽然训练样本占比小,但是他们的预测得分为真平均值较高。
在计算模型准确率时,需要输入如下形式,也就是说需要将0.88这种概率转为0或者1,那么怎么划分,是一个关键问题,常见的有按照阈值划分、取topn划分。接下来思考思考这两个方式的合理性。
predicted_labels = [[1,0,0,0,1],[1,0,0,0,1],[1,0,0,0,1],[1,0,0,0,1],[1,0,0,0,1],[1,0,1,0,1]]
true_labels = [[1,1,0,0,1],[1,0,0,1,1],[1,0,0,0,1],[1,1,1,0,1],[1,0,0,0,1],[1,0,0,0,1]]
1.阈值问题:
这个阈值没有实际意义,添加它,为了调整准招率。
2.topn的问题:
这个n为变量,top没有实际意义,有它,为了调整准招率的阈值
3.如何评估多标签模型
用于多标签,多类别分类的最流行的准确性矩阵是:
- 海明得分
- 汉明损失
- 子集精度
以上三个指标的计算代码:
def hamming_score(y_true, y_pred, normalize=True, sample_weight=None):
'''
Compute the Hamming score (a.k.a. label-based accuracy) for the multi-label case
'''
acc_list = []
for i in range(y_true.shape[0]):
set_true = set( np.where(y_true[i])[0] )
set_pred = set( np.where(y_pred[i])[0] )
#print('\nset_true: {0}'.format(set_true))
#print('set_pred: {0}'.format(set_pred))
tmp_a = None
if len(set_true) == 0 and len(set_pred) == 0:
tmp_a = 1
else:
tmp_a = len(set_true.intersection(set_pred))/\
float( len(set_true.union(set_pred)) )
#print('tmp_a: {0}'.format(tmp_a))
acc_list.append(tmp_a)
return { 'hamming_score' : np.mean(acc_list) ,
'subset_accuracy' : sklearn.metrics.accuracy_score(y_true, y_pred, normalize=True, sample_weight=None),
'hamming_loss' : sklearn.metrics.hamming_loss(y_true, y_pred)}
4.使用多标签损失函数BECwithlogistloss可能会遇见的问题:
1.RuntimeError: Found dtype Long but expected Float
说明此时需要float型数据,但识别到long型数据,此时需要对入参和出参做一下类型转换
关键的两行代码:
output=output.to(torch.float32)
target=target.to(torch.float32)
例子:
output =net(input)
target = variable(t.arange(0,10))
#the point
output=output.to(torch.float32)
target=target.to(torch.float32)
criterion = nn.MSELoss()
loss = criterion(output,target)
net.zero_grad()
print("反向传播之前conv1.bias的梯度")
print(net.conv1.bias.grad)
loss.backward() #此处疑难杂症 先跳过
print("反向传播之后conv1.bias的梯度")
print(net.conv1.bias.grad)
参考:
1.损失函数:https://www.jianshu.com/p/0062d04a2782