Multiclass SVM(多类别SVM分类)关于其 loss function 的求导

2023-05-16

       这两天学习cs231n的课程,顺便做一做该课程配套的作业。在assignment1中有遇到用multiclass SVM来对cifar10进行分类的问题。其中,为了进行训练,需要计算loss 和相关梯度。由于自己太菜,花了好长时间才完整梯度的求解。所以在这里记录一下具体的loss function关于权重参数矩阵W的求导过程。不足之处还请多多指教。

1. loss function

    假设有训练数据X\in \mathbb{R}^{N \times D} ,N为训练数据的个数,D为每个训练数据的维度。训练数据X对应于相应的标签y \in \mathbb{R} ^{N} .假设一共有类别数C。那么对于multiclass SVM来说,其loss function 可以表示如下:

                                           L=\frac{1}{N}\sum_{i=1}^{N}\sum_{j\neq y_i}^{C}max(0,X_{i}*W_j-X_{i}*W_{y_i}+1) + \lambda \sum W^2\cdots \cdots \cdots \cdots (1)    

其中,W为我们需要训练的权重,其维度为W \in \mathbb{R}^{D \times C}。Xi 表示矩阵X的第i行,Wj表示矩阵W的第j列。yi表示数据Xi对应的正确标签值(对于cifar10 来说,总共有10类,yi的取值在0-9之间)。我们通过X \cdot W \in \mathbb{R}^{N \times C}来获得每一个数据在不同类别下的得分。\lambda为正则项系数。

    上式中max的含义是:对于第i个数据Xi与权重矩阵W的每一列Wj相乘,得到的是第i个数据对第j类的得分。只有Xi对正确的那一类yi的得分比其他类j的得分大1, 该部分的值才为0。否则,就有一个大于零的值加到loss函数中。

2. 两种计算梯度(导数)的形式

    要求L对于W的梯度dW,就是求L对于W的导数。这里有两种形式。第一种是将W分成列,一次求解L对每一列的导数,这样累到一块就是LW的导数;第二种方式是通过某种手段直接求LW的梯度。接下来我们分别对这两种方法进行说明。

2.1 分块求解

    通过式(1)可以看到,在很多情况下max得到的值是0,所以这种情况下导数为零。我们不妨将dW初始化为全零矩阵 dW=np.zeros([D,C]),然后只对非零部分进行修改即可。

    当(X_{i}*W_j-X_{i}*W_{y_i}+1)>0时,式(1)就可以简化成如下形式:

                                      L=\frac{1}{N}\sum_{i=1}^{N}\sum_{j\neq y_i}^{C}(X_{i}*W_j-X_{i}*W_{y_i}+1) \cdots \cdots \cdots (2)

下边我们对dW求解的前提都是基于(X_{i}*W_j-X_{i}*W_{y_i}+1)>0。因为当这个条件不满足时,对应的dW0

按照前边所说,分块纠结就是依次对W的每一列求导。所以我们可以得到下式

                                              \frac{\partial L}{\partial W_j}=\frac{1}{N}\sum_{i=1}^{N}\frac{\partial L_i}{\partial W_j} \cdots \cdots \cdots(3)

                                             L_i = \sum_{j\neq y_i}^{C}(X_{i}*W_j-X_{i}*W_{y_i}+1) \cdots \cdots \cdots (4)

此时,我们需要分两种情况讨论:

1、 当j \neq y_i

                                               \frac{\partial L_i}{\partial W_j} =X_i \cdots \cdots (5)

2、 当 j=y_i

                                          \frac{\partial L_i}{\partial W_{y_i}} =-\sum_{j \neq y_i}^{C}X_i \cdots \cdots (6)

注意,对于式(6),并不是单纯地将C-1Xi相加。因为对于某个j,很有可能计算得到的(X_{i}*W_j-X_{i}*W_{y_i}+1)\leq 0,这种情况下得到的值是0。所以我们需要统计满足(X_{i}*W_j-X_{i}*W_{y_i}+1)>0对应的j的个数,用这个系数乘Xi.

下边放代码

def svm_loss_naive(W, X, y, reg):
  """
  Structured SVM loss function, naive implementation (with loops).

  Inputs have dimension D, there are C classes, and we operate on minibatches
  of N examples.

  Inputs:
  - W: A numpy array of shape (D, C) containing weights.
  - X: A numpy array of shape (N, D) containing a minibatch of data.
  - y: A numpy array of shape (N,) containing training labels; y[i] = c means
    that X[i] has label c, where 0 <= c < C.
  - reg: (float) regularization strength

  Returns a tuple of:
  - loss as single float
  - gradient with respect to weights W; an array of same shape as W
  """

  dW = np.zeros(W.shape) # initialize the gradient as zero

  # compute the loss and the gradient
  num_classes = W.shape[1]
  num_train = X.shape[0]
  loss = 0.0
  
  for i in xrange(num_train):
    scores = X[i].dot(W)
    correct_class_score = scores[y[i]]
    for j in xrange(num_classes):
      margin = scores[j] - correct_class_score + 1 # note delta = 1
      if margin > 0:
        if j == y[i]:
            continue    #do nothing when j=y[i]
        loss += margin  #the loss would increase only when margin>0
        dW[:,j]+=X[i].T   #corresponding to equation(5)
        dW[:,y[i]]+=-X[i].T  #corresponding to equation(6). The condition margin>0 is satisfied, so it could be accumulated to d(L_i)/d(W_yi) 
        
  dW=dW/num_train  #1/N
  
  # Right now the loss is a sum over all training examples, but we want it
  # to be an average instead so we divide by num_train.
  loss /= num_train

  # Add regularization to the loss.  element-wise multiply
  loss += reg * np.sum(W * W)  

  #############################################################################
  # TODO:                                                                     #
  # Compute the gradient of the loss function and store it dW.                #
  # Rather that first computing the loss and then computing the derivative,   #
  # it may be simpler to compute the derivative at the same time that the     #
  # loss is being computed. As a result you may need to modify some of the    #
  # code above to compute the gradient.                                       #
  #############################################################################

  dW=dW+2*reg*W  #don't forget the derivation of regularization

  return loss, dW

 

2.2  直接求解

    通过2.1的分析,我们可以看出来,实际上dW中的元素都是由Xi构成的。所以我们可以考虑这样一个问题,是否存在一个矩阵M,使得XiM作用后可以直接得到dW。即dW=X^{T}} \cdot M。 其中X\in \mathbb{R}^{N \times D}M \in \mathbb{R}^{N \times C}

    在2.1中,我们通过对i进行循环,通过X[i].dot(W)来算每一个数据Xi在各个类别下的得分。在这里,既然是直接求解,就不能用循环。我们直接用scores=X.dot(W) 来求得一个N*C的矩阵,里边存放着不同对数Xi对不同类别的得分,对应式(2)中的X_i *W_j

    然后,我们可以通过scores得到X_i*W_{y_i}。 代码实现为:

X_correct_scores=scores[range(num_train),y].reshape(-1,1)

    上边这一句的代码的含义是: y中存放着X中每一个数据Xi的正确标签值,且y \in \mathbb{Z}^{N},num_train 在这里就是N。所以scores[range(num_train),y]实际上就是取出scores中 第i行,第yi列的数据。该数据就是Xi对于正确类别yi的得分。然后将得到是数据变成N*1的矩阵。

    有了scoresX_correct_scores,我们就可以计算非常重要的(X_{i}*W_{j}-X_{i}*W_{y_i}+1)。这里将其结果记为margin. 对应的代码为:

margin=scores-X_correct_scores+1

注意,这里得到的margin是包含了(X_{i}*W_{y_i}-X_{i}*W_{y_i}+1)=1这一项的。而原本的计算公式 式(2)中要求j \neq y_i。 所以我们需要将margin中第i行, 第yi列减去1:

margin[range(num_train),y]-=1

    然后我们将margin中所有元素加和并求平均,再补充上正则项就可以求得loss function的值了。

    此时,我们手中有一个margin矩阵,里边包含了我们所需要的重要信息——所有位置的 (X_{i}*W_{j}-X_{i}*W_{y_i}+1)的值。通过2.1的分析我们知道,要求解dW,一个很重要的条件是(X_{i}*W_j-X_{i}*W_{y_i}+1)>0,即margin>0。接下来,我们来看看如何用margin来构造矩阵M,从而直接求得dW。

    我们首先通过:

sign=margin>0

来得到margin中各个位置的符号。sign为一个N*C的矩阵,里边的元素为True和False, 表示margin中对应位置的元素是否大于0.

然后我们可以通过sign来统计对于每一个Xi,有多少个Wj满足margin>0。这对我们求解式(6)即{\partial L_i}/{\partial W_{y_i}}非常重要:

mark=np.sum(sign,axis=1)

我们将sign按每一行相加,就可以得到每一个Xi对应的margin>0的个数。

此时,我想再强调一下各个矩阵的维度,方便理解:

X \in \mathbb{R}^{N \times D},  margin \in \mathbb{R}^{N \times C}M \in \mathbb{R}^{N \times C}W,dW \in \mathbb{R}^{D \times C}

其中N为数据X的个数, D为每一个数据Xi的维度, C为所有数据可能的类别。我们要通过X,margin,M来得到dW,为了满足矩阵乘法运算的维度要求,显然有 dW=X^{T} \cdot M

                                            dW=[X_{1}^{T},X_{2}^{T}, \cdots, X_{N}^{T} ]_{D\times N} \left[ \begin{matrix} m_{11} & m_{12} & \cdots& m_{1C} \\ \vdots & \vdots & &\vdots \\ m_{N1}& m_{N2} & \cdots &m_{NC} \end{matrix} \right] _{N\times C}\tag{3}

1、 当j \neq y_i

                                               \frac{\partial L_i}{\partial W_j} =X_i \cdots \cdots (5)

2、 当 j=y_i

                                          \frac{\partial L_i}{\partial W_{y_i}} =-\sum_{j \neq y_i}^{C}X_i \cdots \cdots (6)

j \neq y_i时,我们只需要margin中第i行第j列的元素大于零,此时{\partial L_i}/{\partial W_{j}}就为Xi,也就是说与Xi相乘的系数为1。所以M中第i行第j列的元素值为1. 举个例子: 对于i=1来说。 如果margin的第1行第2列的元素大于零(2 \neq y_1),则dW的第2列就存在X1,此时为了让dW的第二列存在X1,则m12应该为1。对于i=2来说,如果margin的第2行的第二列元素大于零(2 \neq y_2),则dW的第2列还应存在X2,此时,为了让dW第二列存在X2,则m22应该为1。而dW第二列中X1和X2应该相加起来,从而满足公式(3)。

我们将M矩阵初始化为全零矩阵,然后对于margin大于零的部分,我们将在M矩阵的对应位置赋值为1。注意,margin大于零的部分已经排除了j=y_i的可能。因为我们之前求解margin时, margin(i,j=y_i)的值为0。至此,我们解决了式(5)。具体的代码如下:

M=np.zeros(sign.shape)
M=M+sign  #sign中True的位置对应margin>0的部分,所以可以直接相加

下来我们来看式(6)的求解。

通过上边的讲解,我们可以推得,当j=y_i时,与Xi相乘的系数不再是1,而是j \neq y_i时,margin(i,j)>0的个数。前边通过mark,我们得到了margin每一行i中大于零的个数mark_i。然后我们将M矩阵对应的M(i,y_i)部分的系数改成-mark_i即可。具体代码如下:

M[range(num_train),y]=-mark

至此,我们就求得了M矩阵的所有元素。然后通过dW=X^{T} \cdot M可以得到对应的dW。别忘记还有正则项的导数部分2*reg*W。

下边是完整的代码:

def svm_loss_vectorized(W, X, y, reg):
  """
  Structured SVM loss function, vectorized implementation.

  Inputs and outputs are the same as svm_loss_naive.
  """
  loss = 0.0
  dW = np.zeros(W.shape) # initialize the gradient as zero
  num_train = X.shape[0]
  num_type = W.shape[1]
  #############################################################################
  # TODO:                                                                     #
  # Implement a vectorized version of the structured SVM loss, storing the    #
  # result in loss.                                                           #
  #############################################################################
  scores = X.dot(W)
  X_correct_scores=scores[range(num_train),y].reshape(-1,1)
  margin=scores-X_correct_scores+1
  margin[range(num_train),y]-=1
  loss=np.sum(np.maximum(margin,0))/num_train+reg*np.sum(W*W)
  #############################################################################
  #                             END OF YOUR CODE                              #
  #############################################################################


  #############################################################################
  # TODO:                                                                     #
  # Implement a vectorized version of the gradient for the structured SVM     #
  # loss, storing the result in dW.                                           #
  #                                                                           #
  # Hint: Instead of computing the gradient from scratch, it may be easier    #
  # to reuse some of the intermediate values that you used to compute the     #
  # loss.                                                                     #
  #############################################################################
  sign=margin>0   #score>0的部分标记为true   N*C
  mark=np.sum(sign,axis=1)  #N*1 对于每一个数据X[i],其得分大于0的个数。
  M=np.zeros(sign.shape)
  M[range(num_train),y]=-mark  #当j=yi的时候,dLi/dw_yi 应该等于Xi*一个系数(即mark)
  M=M+sign #对于margin大于零且j!=yi的部分(这部分已经除去了j=yi的情况。因为j=yi时,margin对应的部分等于0),系数为1
  dW=X.T.dot(M)/num_train+2*reg*W

  #############################################################################
  #                             END OF YOUR CODE                              #
  #############################################################################

  return loss, dW

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Multiclass SVM(多类别SVM分类)关于其 loss function 的求导 的相关文章

随机推荐

  • GPS坐标用于机器人定位的简单处理

    文章目录 前言一 GPS数据格式二 GPS坐标转换二维坐标原理三 参考代码1 转换经纬度格式2 解析通过串口获得的NMEA数据3 将经纬度转换为xy平面二维坐标 前言 最近工作上面接触使用GPS的NMEA数据为机器人提供平面坐标定位 xff
  • 学完C++基础后再学什么?

    学完 xff1f 那是什么程度 xff1f STL用得熟练吗 xff1f 算法和数据结构掌握得怎么样呢 xff1f 会写界面吗 xff1f BOOST呢 xff1f 像楼上所说的换一种语言 xff0c 简直是痴人说梦 xff0c 如果不深入
  • 视觉SLAM十四讲:回环检测-知识点+代码

    目录 基于外观的几何关系1 基础知识1 1 准确率和召回率1 2 词袋模型1 3 字典1 4 字典的数据结构1 5 相似度的计算1 6 相似度评分的处理1 7 检测回环后的验证 2 实践与代码解析2 1 创建字典2 2 相似度计算 回环检测
  • QT笔记--QT内类的层次关系,以及控件从属关系

    QT窗口界面使用的类层次如下 只包含了直接使用部分 界面上每一个创建的控件 xff0c 都是一个控件类的对象 xff0c 定义在头文件ui mainwindoow h的类UI MainWindow中 xff0c 并且其中的成员函数setup
  • C_带参数的宏定义

    C 带参数的宏定义 xff23 语言允许宏带有参数 在宏定义中的参数称为形式参数 xff0c 在宏调用中的参数称为实际参数 对带参数的宏 xff0c 在调用中 xff0c 不仅要宏展开 xff0c 而且要用实参去代换形参 带参宏定义的一般形
  • 十进制数转换成十六进制数~C语言

    include lt stdio h gt 下面将整数a转换成十六进制输出的字符串 原理 xff1a 1 xff0c 首先知道0b100000 61 0b10000 2 61 0b1000 2 61 0b100 2 61 0b10 2 利用
  • Qt实现线程安全的单例模式

    实现方式 1 实现单例 把类的构造函数 拷贝构造函数 赋值操作符定义为private的 xff1b 把获取单例的接口和唯一的实例指针定义为static的 xff0c 不需要实例化 xff0c 直接通过类名即可访问 2 支持多线程 采用双重校
  • 文本文件和二进制文件的差异和区别

    广义上的二进制文件包括文本文件 xff0c 这里讨论的是狭义上的二进制文件与文本文件的比较 xff1a 能存储的数据类型不同 文本文件只能存储char型字符变量 二进制文件可以存储char int short long float 各种变量
  • Qt实现记录日志文件log

    概述 Qt有两种实现记录日志的方式 xff0c 第一种是安装自定义的Qt消息处理程序 xff0c 自动输出程序产生的调试消息 警告 关键和致命错误消息的函数 xff1b 第二种是自定义一个类 xff0c 可以在程序指定位置打印输出指定的内容
  • Qt在linux环境下调用动态库,pro工程文件加载库和QLibrary加载库两种方式

    QT调用动态库 xff0c 在编译时和运行时的方式不同 xff0c 编译时可在pro文件加载或使用QLibrary类加载 xff1b 运行时依赖环境变量 xff0c windows下直接把动态库拷贝到可执行文件目录即可 xff0c linu
  • linux下QT发布程序双击打不开解决方法

    现象 Qt开发的程序 xff0c 使用 终端可以打开 xff0c 双击却打不开 阶段一 右键可执行程序 xff0c 选择属性 xff0c 可执行程序类型如果是 application x sharedlib xff0c 在QT的pro文件添
  • Qt发起http请求,get和post方式,并接收响应数据

    目录 Qt发起http请求get xff0c 异步接收Qt发起http请求post xff0c 异步接收Qt发起http请求get和post xff0c 收发同步http下载网络图片 Qt发起http请求get xff0c 异步接收 get
  • QT实现浏览器访问网页,使用QWebEngineView

    支持访问网页 xff0c 前进 后退 刷新 xff0c 点击超链接自动跳转 xff0c 获取网页鼠标事件 xff0c 重新编译QWebEngineView库后还可以支持播放mp4等视频 xff1b Qt在debug模式运行有时访问网页很卡
  • Qt程序打包成安装包exe

    本章介绍把Qt开发的程序打包成安装包的方法 xff0c 程序打包成install exe xff0c 可双击安装 xff0c 有默认安装路径 xff0c 也可以选择安装目录 xff0c 自动生成桌面快捷方式和开始菜单选项 xff0c 可以在
  • C/C++socket网络编程

    目录 tcp和udp通信流程图socket函数bind函数listen函数accept函数connect函数recv recvfrom read函数send write sendto sendmsg函数close shutdown函数hto
  • OSPF路由协议解释

    目录 OSPF路由协议OSPF数据包类型OSPF邻区状态OSPF的邻接关系建立过程 路由名词解释OSPF开源项目 OSPF路由协议 OSPF简介 1 xff08 Open Shortest Path First xff09 xff0c 开放
  • redis服务搭建,C++实现redis客户端,redis远程可视化工具

    目录 redis简介redis服务搭建redis常用命令C 43 43 实现redis客户端redis远程可视化工具 Another Redis DeskTop Manager redis简介 官方网址 xff1a https redis
  • Dijkstra算法图解,C++实现Dijkstra算法

    目录 Dijkstra算法简介数据结构抽象初始化开始计算第一轮计算第二轮计算第三轮计算第四轮计算算法总结 C 43 43 实现Dijkstra算法 Dijkstra算法简介 Dijkstra算法计算是从一个顶点到其余各顶点的最短路径算法 x
  • python实现ID3决策树分类算法

    所有的分类与回归算法中心思想大致是一样的 xff0c 那就是根据现有带标签的数据集训练一个分类器模型 xff0c 然后对待未知的样本 xff0c 根据训练好的分类模型来判定它属于哪个类 分类与回归的区别在我看来就是标签连续与否的区别 xff
  • Multiclass SVM(多类别SVM分类)关于其 loss function 的求导

    这两天学习cs231n的课程 xff0c 顺便做一做该课程配套的作业 在assignment1中有遇到用multiclass SVM来对cifar10进行分类的问题 其中 xff0c 为了进行训练 xff0c 需要计算loss 和相关梯度