交叉熵损失函数python_【DL-CV】损失函数,SVM损失与交叉熵损失

现在有一个模型,能对输入的图像各种可能的类别进行评分。我们会引入损失函数Loss Function(或叫代价函数 Cost Function)定量的衡量该模型(也就是权重W)的好坏,其原理是——输出结果与真实结果之间差异越大,损失函数输出越大,模型越糟糕(需要训练让损失变小)。

根据差异定义的不同,损失函数有不同的计算公式,这里介绍在图像识别中最常用的两个损失——多类别SVM损失(或折叶损失hinge loss)和交叉熵损失,分别对应多类别SVM分类器和Softmax分类器

而且为了方便介绍,我们继续以图片评分的例子为例

多类别SVM损失

第i个数据中包含图像xi 的像素和代表正确类别的标签yi(一个代表类别的数字),xi经过模型后输出sj (j对应某个类别的数字,sj对应该类别的分数),了解这些后我们先抛出每个数据损失计算公式:

模型越好,正确类别的得分应该要比其他错误类别的得分高,至于高多少,这个阈值(Δ)由我们来定,如果高出阈值,我们认为正确类别和某个类别的区分很好,我们给一个0损失给这两个类别的区分。相反如果某个错误类别比正确类别的得分高,说明该模型对这两类别的区分很糟,我们把高多少这个值加上阈值作为其损失。求正确类别和其他错误类别两两的损失值的和作为该数据的总损失。

一个具体例子如下图

这种损失也叫折叶损失,因其使用max(0,-)而得名,是标准常用的用法。有时候也会使用平方折叶损失SVM(L2-SVM),它使用的是max(x,-)2,这会放大损失(对坏的方面更加敏感),有些数据集使用L2-SVM会有更好的效果,可以通过交叉验证来决定到底使用哪个。

正则化!!

这里插入讲下正则化的问题

上面损失函数有一个问题。假设有一个数据集和一个权重集W能够正确地分类每个数据(对于所有的i都有Li=0)时,这个W并不唯一,比如当λ>1时,任何数乘λW都能使得损失值为0,因为这个变化将所有分值的大小都均等地扩大了;又比如可能存在W的某一部分很大,另外一部分几乎为0。我们并不想要一堆扩大了N倍的W或者极不均匀的W,这时候就要向损失函数增加一个正则化惩罚(regularization penalty)R(W)来抑制大数值权重了。

需要注意的是正则化损失R(W)并不是加在每个数据的损失上,而是加在所有一组训练集(N个数据)损失的平均值上,这样我们得到最终的损失函数L,λ是正则化强度。

最常用的正则化惩罚是L2范式,L2范式通过对所有参数进行逐元素的平方惩罚来抑制大数值的权重:

正则化很重要,正则化的作用也不止于此,更多作用后面会在介绍,在此你只要知道正则化的作用是提升分类器的泛化能力,每个损失函数都应该引入 λR(W)

关于阈值Δ

Δ是一个超参数,该超参数在绝大多数情况下设为Δ=1.0就行了。

权重W的大小对于类别分值有直接影响(当然对他们的差异也有直接影响):当我们将W中值缩小,类别分值之间的差异也变小,反之亦然。因此,不同类别分值之间的边界的具体值(比如Δ=1或Δ=100)从某些角度来看是没意义的,因为权重自己就可以控制差异变大和缩小。也就是说,真正的权衡是我们允许权重能够变大到何种程度(通过正则化强度λ来控制)。

交叉熵损失

先来了解Softmax函数,这是一种压缩函数,实现归一化的

$$P(s)={e^{s_k}\over \sum_je^{s_j}}$$

Softmax函数接收一组评分输出s(有正有负),对每个评分sk进行指数化确保正数,分母是所有评分指数化的和,分子是某个评分指数化后的值,这样就起到了归一化的作用。某个类的分值越大,指数化后越大,函数输出越接近于1,可以把输出看做该类别的概率值,所有类别的概率值和为一。如果正确类别的概率值越接近0,则该模型越糟,应用这个特性,我们通过对正确类别的概率值取-log来作为损失(正确类别的概率越小,损失越大),于是我们得到

$$L_i=-log({e^{s_{y_i}}\over \sum_je^{s_j}})$$

这就是交叉熵损失计算公式(有时也叫非正式名Softmax损失),具体例子如下图,使用上不要忘记加正则化λR(W)哦

防止计算溢出

因为交叉熵损失涉及指数函数,如果遇上很大或很小的分值,计算时会溢出。s很大es会上溢出;s是负数且|s|很大,es会四舍五入为0导致下溢出,分母为0就不好了。为解决这个潜在的问题,我们要在计算前对得分数据处理一下。取所有得分的最大值M = max(sk), k=1,2,3...,令所有得分都减去这个M。这不会影响损失Softmax函数的输出,自然也不会影响损失,但这一下解决了溢出问题。要证明也很简单:es-M = es / eM , 而分子分母会约掉 eM

但仍然存在一个问题,如果分子发生下溢出导致Softmax函数输出0,取对数时就会得到−∞,这是错误的。为解决这个问题,其实我们把上面的变换代进去继续算就会发现自己解决了

求和项里一定会有一个e0=1,最终对大于1和取对数不会发生溢出了,最后损失公式变成这样:

$$L_i=-\log({e^{s_{y_i}}\over \sum_je^{s_j}})=-\log({e^{({s_{y_i}}-M)}\over \sum_je^{({s_j}-M)}})=\log(\sum_j{e^{(s_j-M)}})-(s_{y_i}-M)$$

两者比较

我们用一组数据来探究它们的区别(假设SVM损失中的Δ=1),有三组输出分数[10,-2,3], [10,9,9],[10,-100,-100],正确类别的得分都是10,易得三组数据的SVM损失都是0,但它们的交叉熵损失明显是有高低之分的。对于SVM损失,它关心的是边界区分,正确类别的得分其他得分高出Δ就完事了,损失为0了。但对于交叉熵损失,由于正确类别的概率与分数间的差异是有关的,损失不可能等于0,正确类别的得分无穷大,其他得分无穷小,损失才趋于0。换句话说,交叉熵损失永远有缩小的空间,它希望评分模型完美;而SVM损失只需要评分模型好到一定程度就行了。

但实际使用上,它们经常是相似的,通常说来,两种损失函数的表现差别很小,大可不必纠结使用哪个

代码实现

注:以下代码基于单层网络并且不考虑激活函数(图像数据与权重相乘得到分数)进行损失统计,目的是为了集中介绍损失函数的numpy实现。x是二维数组,是N个样本的数据,每行是该样本的像素数据(已展开),因此这里采用x*W。y是一维数组,包含每个样本的真实类别(一个数字)。

import numpy as np

# SVM损失函数实现

def svm_loss_naive(W, x, y, reg):

"""

循环实现

"""

train_num = x.shape[0] # 样本数量

classes_num = W.shape[1] # 类别数量

loss = 0.0

for i in range(train_num): # 计算某个样本的损失值

scores = x[i].dot(W)

correct_class_score = scores[y[i]] # 提取该样本的真实类别分数

for j in range(classes_num): # 正确类别得分与其他得分比较

if j == y[i]:

continue

margin = scores[j] - correct_class_score + 1 # 这里设阈值为1

if margin > 0: # 造成损失,将其计入

loss += margin

loss /= train_num

loss += reg * np.sum(W * W) # 加上正则化损失

return loss

def svm_loss_vectorized(W, x, y, reg):

"""

最高效向量化运算,维持x的二维结构运算

"""

train_num = x.shape[0]

classes_num = W.shape[1]

scores = x.dot(W) # 二维结构,每行是该样本各个类别的得分

correct_class_scores = scores[np.arange(train_num), y] # 提取每个样本的真实类别分数

correct_class_scores = np.repeat(correct_class_scores,classes_num).reshape(train_num,classes_num) # 扩展至二维结构(与scores同形状),每一行都是该样本真实类别的得分

margins = scores - correct_class_scores + 1.0

margins[range(train_num), y] = 0 # 令正确类别与自身相比的loss为0 抵消 +1.0

loss = (np.sum(margins[margins > 0])) / train_num # 把正数(loss)全加起来除以样本数得最终损失

loss += reg * np.sum(W*W) # 加上正则化损失

return loss

import numpy as np

# Softmax 损失函数实现

def softmax_loss_naive(W, x, y, reg):

"""

循环实现

"""

loss = 0.0

classes_num = W.shape[1] # 类别数量

train_num = x.shape[0] # 样本数量

for i in range(train_num): # 计算某个样本的损失值

score = x[i].dot(W)

score -= np.max(score) # 减去最大值防指数运算溢出

correct_class_score = score[y[i]] # 提取该样本的真实类别分数

exp_sum = np.sum(np.exp(score))

loss += np.log(exp_sum) - correct_class_score # 每个样本的损失叠加

loss /= train_num

loss += 0.5 * reg * np.sum(W*W) # 加上正则化损失

return loss

def softmax_loss_vectorized(W, x, y, reg):

"""

最高效向量化运算,维持x的二维结构运算

"""

classes_num = W.shape[1]

train_num = x.shape[1]

scores = x.dot(W) # 二维结构,每行是该样本各个类别的得分

scores -= np.repeat(np.max(scores, axis=1), classes_num).reshape(scores.shape) # 减去最大值防指数运算溢出

exp_scores = np.exp(scores) # 指数化

correct_class_scores = scores[range(train_num), y] # 提取每个样本的真实类别分数

sum_exp_scores = np.sum(exp_scores, axis=1) # 每个样本的指数和

loss = np.sum(np.log(sum_exp_scores) - correct_class_scores) # 所有样本总损失

loss /= train_num

loss += reg * np.sum(W*W)

expand_sum_exp_scores = np.repeat(sum_exp_scores, classes_num).reshape(scores.shape) # 对每个样本的指数和进行扩展,与scores进行除法运算

return loss

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值