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

【DL-CV】线性分类器<前篇---后篇>【DL-CV】反向传播,(随机)梯度降低python

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

根据差别定义的不一样,损失函数有不一样的计算公式,这里介绍在图像识别中最经常使用的两个损失——多类别SVM损失(或折叶损失hinge loss)和交叉熵损失,分别对应多类别SVM分类器和Softmax分类器
并且为了方便介绍,咱们继续以图片评分的例子为例数组

多类别SVM损失

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

模型越好,正确类别的得分应该要比其余错误类别的得分高,至于高多少,这个阈值(Δ)由咱们来定,若是高出阈值,咱们认为正确类别和某个类别的区分很好,咱们给一个0损失给这两个类别的区分。相反若是某个错误类别比正确类别的得分高,说明该模型对这两类别的区分很糟,咱们把高多少这个值加上阈值做为其损失。求正确类别和其余错误类别两两的损失值的和做为该数据的总损失。
一个具体例子以下图
图片描述函数

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

正则化!!

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

上面损失函数有一个问题。假设有一个数据集和一个权重集W可以正确地分类每一个数据(对于全部的i都有Li=0)时,这个W并不惟一,好比当λ>1时,任何数乘λW都能使得损失值为0,由于这个变化将全部分值的大小都均等地扩大了;又好比可能存在W的某一部分很大,另一部分几乎为0。咱们并不想要一堆扩大了N倍的W或者极不均匀的W,这时候就要向损失函数增长一个正则化惩罚(regularization penalty)R(W)来抑制大数值权重了。
须要注意的是正则化损失R(W)并非加在每一个数据的损失上,而是加在全部一组训练集(N个数据)损失的平均值上,这样咱们获得最终的损失函数L,λ是正则化强度。
图片描述blog

最经常使用的正则化惩罚是L2范式,L2范式经过对全部参数进行逐元素的平方惩罚来抑制大数值的权重:
图片描述图片

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

关于阈值Δ

Δ是一个超参数,该超参数在绝大多数状况下设为Δ=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
相关文章
相关标签/搜索