精华内容
下载资源
问答
  • 伪仿射投影算法优化迭代步长的研究
  • MATLAB程序m文件和simulink程序,用粒子群算法做控制器去优化迭代学习控制,鲁棒性好,且迭代精度高,有PDF详细解释
  • X射线双能计算机层析成像投影分解的优化迭代方法
  • 离散线性时不变系统的拟牛顿型优化迭代学习控制
  • 针对此难题,提出一种基于关键点提取与优化迭代最近点(ICP)的点云配准算法。在粗配准中,将体素格滤波与法向距离关键点的提取相结合,计算关键点的快速点特征直方图以进行特征匹配,然后采用对应关系估计优化随机采样...
  • 最近邻插值 与 双线性插值算法 优化迭代 的 0.5 像素之差

    Python OpenCV 365 天学习计划,与橡皮擦一起进入图像领域吧。本篇博客是这个系列的第 43 篇。
    该系列文章导航参考:https://blog.csdn.net/hihell/category_10688961.html

    基础知识铺垫

    先补齐一下昨天文章发布出去的一个小坑,最后一段代码实现之后,发现运行之后图像边缘出现了很多锯齿。

    Python OpenCV 图像的 最近邻插值 与 双线性插值算法 优化迭代

    疑惑的同时,肯定是代码有细节弄差了,复查代码的时候发现问题了,注意下述代码:

    dst[dst_y, dst_x, n] = (1-u)*(1-v)*src[j, i, n]+u*(1-v) * src[j+1, i, n] + (1-u)*v*src[j, i+1, n] + u*v*src[j+1, i+1, n]
    

    与公式比对,这个细致的活你可以自己比对一下。
    f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)

    问题出在图像的行与列上,代码的 u*(1-v) * src[j+1, i, n] + (1-u)*v*src[j, i+1, n] 这个部分我写反了~尴尬

    修改成下述代码,搞定,基本一模一样了。

    dst[dst_y, dst_x, n] = (1-u)*(1-v)*src[j, i, n]+v*(1-u) * src[j+1, i, n] + (1-v)*u*src[j, i+1, n] + u*v*src[j+1, i+1, n]
    

    Python OpenCV 图像的 最近邻插值 与 双线性插值算法 优化迭代

    算法优化

    下面说一下算法优化吧,咱还在 这篇博客 挖下了一个小坑,最近邻插值算法最终的结果不尽人意,图像在放大的时候出现了很强的锯齿。

    Python OpenCV 图像的 最近邻插值 与 双线性插值算法 优化迭代
    先把之前的代码迁移过来,修改成如下格式:

    import cv2 as cv
    import numpy as np
    
    
    def nearest_demo(src, multiple_y, multiple_x):
        src_y, src_x, src_c = src.shape
        tar_x, tar_y, tar_c = src_x*multiple_x, src_y*multiple_y, src_c
        # 生成一个黑色的目标图像
        tar_img = np.zeros((tar_y, tar_x, tar_c), dtype=np.uint8)
        print(tar_img.shape)
        # 渲染像素点的值
        # 注意 y 是高度,x 是宽度
        for y in range(tar_y-1):
            for x in range(tar_x-1):
                # 计算新坐标 (x,y) 坐标在源图中是哪个值
    
                src_y = round(y*src_y/tar_y)
                src_x = round(x*src_x/tar_x)
    
                tar_img[y, x] = src[src_y, src_x]
    
        return tar_img
    
    
    src = cv.imread("./ttt.png")
    print(src.shape)
    cv.imshow("src", src)
    # dsize = (cols,rows) 中文,(宽度,高度)
    dst = cv.resize(src, (src.shape[1]*2, src.shape[0]
                          * 2), interpolation=cv.INTER_NEAREST)
    cv.imshow("dst", dst)
    
    new_dst = nearest_demo(src, 2, 2)
    cv.imshow("new_dst", new_dst)
    
    cv.waitKey(0)
    cv.destroyAllWindows()
    

    第一部分的优化是关于中心点的,这部分说真的,橡皮擦找了很多资料,发现都解释的不太清楚,基本就是到重点的地方就略过了,大意我在进行转述一遍,如果你有好的解释,欢迎在评论区提供给我,重点就是那 0.5 像素的问题。

    上文代码如果想实现和 OpenCV 提供的内置函数一样的效果,重点修改的地方如下:

    srcy = round(y*src_y/tar_y)
    srcx = round(x*src_x/tar_x)
    # 修改如下
    srcy = round((y+0.5)*src_y/tar_y-0.5)
    srcx = round((x+0.5)*src_x/tar_x-0.5)
    

    就是在这个地方直接懵掉了,有的博客中写道 srcX=dstX* (srcWidth/dstWidth)+0.5*(srcWidth/dstWidth-1) 相当于我们在原始的浮点坐标上加上了0.5*(srcWidth/dstWidth-1)

    但是这个地方没有其它解释了,有的博客会用两张图对比着说要源图像与目标图像的几何中心对齐,但是也只是画了两张图加上一些简单的文字描述,摘抄如下:

    假设源图像是 3x3,这个图像的中心点坐标为 (1,1),目标图像为 9x9,中心点坐标是 (4,4),那中心点对齐就应该指的是 (4,4) 点对齐到 (1,1) 点,但是根据 srcx = round(x*src_x/tar_x)srcy = round(y*src_y/tar_y) 公式计算,得到的中心点坐标是 (1.333,1.333) 并不是 (1,1),图像整体偏右下方,现在需要对齐。

    那我们进行一下简单的推算。中心点对齐提前假设源图像不动,那最终应该是存在下列公式。

    源图像 src 中心点坐标 (M12,M12)(\cfrac{M-1}{2},\cfrac{M-1}{2}),目标图像 dst 中心点坐标 (N12,N12)(\cfrac{N-1}{2},\cfrac{N-1}{2})

    将上述值带入公式: srcx=dstx(srcwidthdstwidth)+Ksrcx=dstx*(\cfrac{srcwidth}{dstwidth})+K

    式子转换成 M12=N12MN+K\cfrac{M-1}{2}=\cfrac{N-1}{2}*\cfrac{M}{N}+K

    最后求得 K 的值为 12(MN1)\cfrac{1}{2}*(\cfrac{M}{N}-1)

    MN=srcwidthdstwidth\cfrac{M}{N}=\cfrac{srcwidth}{dstwidth}

    所以 K 其实等于 K=12(srcwidthdstwidth1)K=\cfrac{1}{2}*(\cfrac{srcwidth}{dstwidth}-1)

    对应到代码上,就变成了如下内容:

    # 修改前
    src_y = round(dst_y*src_height/tar_height)
    src_x = round(dst_x*src_width/tar_width)
    # 修改后
    src_y = round(dst_y*src_height/tar_height+1/2*(src_height/tar_height-1))
    src_x = round(dst_x*src_width/tar_width+1/2*(src_height/tar_height-1))
    

    其实这时你在这里一下,就得到最终的结果了。

    # 修改后
    src_y = round((dst_y+0.5)*src_height/tar_height-0.5)
    src_x = round((dst_x+0.5)*src_width/tar_width-0.5)
    

    修改之后,运行感觉比系统内置的效果还出色一些了,哈哈哈。

    Python OpenCV 图像的 最近邻插值 与 双线性插值算法 优化迭代
    相同的优化复制到双线性插值算法中,自己可以对比一下运行的效果。

    关于运行速度,我也找了一些资料,奈何目前掌握的基础知识不够,目前先搁置一下吧,这个系列过 100 篇的时候,我们在聊。

    橡皮擦的小节

    真没有想到,一个 0.5 像素的小问题竟然这么费力。

    希望今天的 1 个小时你有所收获,我们下篇博客见~

    相关阅读


    技术专栏

    1. Python 爬虫 100 例教程,超棒的爬虫教程,立即订阅吧
    2. Python 爬虫小课,精彩 9 讲

    今天是持续写作的第 85 / 100 天。
    如果你想跟博主建立亲密关系,可以关注同名公众号 梦想橡皮擦,近距离接触一个逗趣的互联网高级网虫。
    博主 ID:梦想橡皮擦,希望大家点赞评论收藏

    展开全文
  • 一、从泰勒展开式了解极小值点: 首先在处泰勒展开式: 标量: 向量: 对任意一个函数(标量): ...(一)无约束优化迭代法基本结构: (1)设置参数初始点,设置convergencetolerance (当大于时停止迭代),记...

    一、从泰勒展开式了解极小值点:

    首先f(w)w_{k}处泰勒展开式:

    标量: f(w_{k}+\delta )\approx f(w_{k})+f{}'(w_{k})\delta +\frac{1}{2}f{}''(w_{k})\delta^{2} + \cdots +\frac{1}{k!}f^{k}(w_{k})\delta^{k}+\cdots

    向量:f(w_{k}+\delta )\approx f(w_{k}) + (f{}'(w_{k}))^{T}\delta +\frac{1}{2}\delta^{T} H(w_{k})\delta

    对任意一个函数f(w)(标量):

    1.严格局部极小点值:f(w_{k}+\delta )>f(w_{k} )

    2.等同于满足f{}'(w_{k})=0 且f{}''(w_{k})>0

    向量:

    1.满足局部极小值:f{}'(w_{k})=0 且H(w_{k})>0(H(w_{k})为正定矩阵),当H(w_{k})为不定矩阵时,是一个鞍点。

     

    二、无约束优化迭代法

    (一)无约束优化迭代法基本结构:

    (1)设置参数初始点w_{0},设置convergence tolerance  \theta(当\theta大于(step_{k}-step_{k-1})时停止迭代),记数k=0.

    (2)决定优化方向d_{k}(核心)

    (3)决定步长\alpha _{k}使得f(w_{k}+\alpha _{k}d_{k})对于\alpha _{k}\geqslant 0最小化,w_{k+1}=w_{k} +\alpha _{k}d_{k}

    (4)当\left \| d_{k} \right \|\leq \theta时,停止迭代;否则继续迭代

     

    (二)迭代法

    1、梯度下降法:

    方向选取:d_{k}=-f{}'(w_{k})

    根据泰勒公式:f(w_{k}+d_{k} )\approx f(w_{k}) + (f{}'(w_{k}))^{T}d_{k} +\frac{1}{2}d_{k}^{T} H(w_{k})d_{k}

    仅看前两项(假定海塞矩阵为正定矩阵),要想f(w_{k}+d_{k} )下降,需要f(w_{k})加一个负数,又由于数量向量积余弦值最大为1,因此取d_{k}=-f{}'(w_{k}),此时f(w_{k}+d_{k} )\approx f(w_{k}) - (f{}'(w_{k}))^{T}*f{}'(w_{k})

    优点:下降速度快

    缺点:更容易陷入局部最小值,没有考虑海塞矩阵是否正定

     

    2、牛顿法:

    方向选取:d_{k}=-H^{-1}(w_{k})f{}'(w_{k})

    根据泰勒公式:f(w_{k}+d_{k} )\approx f(w_{k}) + (f{}'(w_{k}))^{T}d_{k} +\frac{1}{2}d_{k}^{T} H(w_{k})d_{k}

    求导得:\frac{\partial }{\partial w_{k}}f(w_{k}+d_{k} )=0\Rightarrow f{}'(w_{k}) + H(w_{k})d_{k}=0\Rightarrow d_{k}=-H^{-1}(w_{k})f{}'(w_{k})

    缺点:海塞矩阵和海塞矩阵的逆很难求

     

    3、拟牛顿法:

    思想:针对牛顿法缺点,不直接求H^{-1}(w_{k}),而是用正定矩阵逼近.

    DFP:

    方向选取:d_{k}=-S_{k}f{}'(w_{k})

    S_{k+1}=S_{k}+\frac{\delta _{k}\delta _{k}^{T}}{\delta _{k}^{T}\gamma _{k}}-\frac{S_{k}\gamma _{k}\gamma _{k}^{T}S_{k}}{\gamma _{k}^{T}S_{k}\gamma _{k}},其中\gamma _{k}=f{}'(w_{k+1})-f{}'(w_{k})\delta _{k} = w _{k+1}-w _{k}

    BFGS:

    方向选取:d_{k}=-S_{k}f{}'(w_{k})

    S_{k+1}=S_{k} + (1+\frac{\gamma _{k}^{T}S_{k}\gamma _{k}}{\delta _{k}^{T}\gamma _{k}})\frac{\delta _{k}\delta _{k}^{T}}{\delta _{k}^{T}\gamma _{k}}-\frac{\delta _{k}\gamma _{k}^{T}S _{k}+S_{k}\gamma _{k}\delta _{k}^{T}}{\delta _{k}^{T}\gamma _{k}}

     

    三、多元回归损失函数梯度下降

    多元回归函数A(x):A(x)=\sum_{i=0}^{k}w_{i}x_{i},k个自变量,k+1个未知参数

    损失函数:S(w)=\frac{1}{2}\sum_{i=1}^{n}(A_{w}(x^{i})-y^{i})^{2},n个样本

    随机梯度抽样x^i,y^iS(w)=\frac{1}{2}(A_{w}(x^{i})-y^{i})^{2}

    w_{k}求偏导\frac{\partial S(w)}{\partial w_{k}}=(A_{w}(x^{i})-y^{i})*\frac{\partial A_{w}}{\partial w_{k}}=(A_{w}(x^{i})-y^{i})x^{i}

    梯度更新:w_{k+1}=w_{k} - \alpha _{k}\frac{\partial S(w)}{\partial w_{k}}=w_{k} - \alpha _{k}(A_{w}(x^{i})-y^{i})x_{i}

    批梯度下降,m个为一批:w_{k+1}=w_{k} - \alpha _{k}\sum_{i=1}^{m}\frac{\partial S(w)}{\partial w_{k}}=w_{k} - \alpha _{k}\sum_{i=1}^{m}(A_{w}(x^{i})-y^{i})x_{i}

    总结:1.批下降梯度法相比以随机梯度下降法更加稳定,且受个别值影响更低。

    2.对于不同的样本值及样本量,损失函数是有细微差别的,因此每次梯度下降时是向着该次损失函数最低点下降,反映在总体样本损失函数上就是,每次计算的d_{k}不直接指向最低点,方向角度会左右偏移一点。

     

    展开全文
  • 该方法把统计优化手段导入循环迭代法,可适用于费米函数这一类曲线变化范围大、且有多个自变量的函数.实验上用焦磷酸质子交换制备了LiNbO3波导样品,用该方法对退火后的折射率分布作了测量拟合,折射率拟合值与实验值的...
  • 优化迭代算法写在前面:Karpathy推荐Adam作为默认算法,如果full batch的话,在去除所有噪声的情况下尝试L-BFGS(一种二阶优化算法,详情请自行搜索)。关于优化算法的实现请参考CS231n作业笔记2.3:优化算法...

    CS231n简介

    详见 CS231n课程笔记1:Introduction
    注:斜体字用于注明作者自己的思考,正确性未经过验证,欢迎指教。

    优化迭代算法

    写在前面:Karpathy推荐Adam作为默认算法,如果full batch的话,在去除所有噪声的情况下尝试L-BFGS(一种二阶优化算法,详情请自行搜索)。关于优化算法的实现请参考CS231n作业笔记2.3:优化算法Momentum, RMSProp, AdamCS231n作业笔记1.4:随机梯度下降(SGD)


    1. SGD (Simple Gradient Descent Update Stochastic gradient descent)

    最简单原始的迭代算法,就是减去learning_rate*梯度值。
    SGD
    Stochastic的名字好像是对比于全训练集训练方法的,每次只使用训练集中的一小部分(batch),具体请参照Optimization: Stochastic Gradient Descent
    这个算法具有的问题之一就是会根据梯度值的大小决定步长,所以如果各个维度的步长相差很大,就会不断震荡,而且收敛缓慢,如图所示。
    jitter

    2. Momentum动量法

    如图所示,把更新迭代的问题考虑成为物理问题,模拟为使得小球收敛到谷底的问题。那么梯度值对应于小球在某一点所受到的力(加速度),参数每次更新的量对应于小球当时的速度,参数值对应于小球当时的高度(位置,能量)。注意这里力是不会直接影响高度的,而是通过更改速度,间接影响高度。上诉模拟方式,也是Momentum的由来。
    Momentum1
    优化算法如下:
    Momentum
    其中v对应于速度,dx对应于加速度,x则对应于高度。同时为了使得小球可以收敛于谷底,加入了能量损失机制,即每次在mu*v的基础上改变。mu的值通常是0.5,0.9,0.99。v被初始化为0.
    回答SGD中的问题,Momentum通过对于速度的修改间接影响高度,所以具有延迟作用以及累加作用,有效的解决了震荡的问题。同时因为会累加小梯度的维度上的速度值,所以可以加快收敛速度。

    3. Nesterov Momentum update

    此算法是对Momentum的改进算法,不是在当前位置求梯度,而是每次都向前看一步,使用未来的梯度值。如上节所诉,对于Momentum算法,如果不考虑每次因为梯度值带来的v的变化,下一刻x会变为x+mu*v(这里就叫做向前看一步),这里的梯度值应该更加有助于收敛(被证明),如下图所示。
    Nesterov1
    原始公式如下图所示:
    Netsterov2
    注意到这里求得梯度值不再是当前位置的梯度值,所以和SGD以及Momentum这种原始模型的接口不兼容。通过对原公式进行形式转换,可以使得接口兼容。如下图所示:
    Netsterov

    4. AdaGrad

    注意到上诉三种算法中的learning rate不变,从而每次更新的步长变化不大。但实际应用中,我们在开始的时候需要高学习率收敛到最优值附近,然后使用低学习率收敛到最优值(因为如果步长过大,参数会在最优点附近震荡而不收敛(此处的震荡无法被Momentum解决))这里就需要引入自适应的算法,可以在迭代过程中合理的减小学习率,使得收敛效果更好。
    AdaGrad是第一个自适应算法,通过每次除以根号下之前所有梯度值的平方和,从而使得步长单调递减。同时因为cache的变化与每个维度上的值有关,所以此方法可以解决各个维度梯度值相差较大的问题。
    AdaGrad
    注意到在AdaGrad算法中,学习率是单调递减的,有的时候这种调整步长的方式过于贪婪了,即可能使得学习率过早的降低,最后停留在最优点较远的位置。RMSprop就是对于AdaGrad的改进版本。

    5. RMSProp

    简单的说,RMSProp对于AdaGrad的改进在于cache的更新方法。不同于不断的累加梯度的平方,RMSProp中引入了泄露机制,使得cache每次都损失一部分,从而使得步长不再是单调递减了。
    RMSProp

    6.Adam

    Adam是Karpathy在课上推荐的默认迭代算法,可以理解为Momentum和RMSProp的融合,同时为了处理初始几次迭代中m和v过小的问题,引入了偏差纠正。
    具体算法如下图所示。(下图PPT中的方程与原论文不符,正确方法见下文)其中beta1通常设为0.9,而beta2通常设为0.995。
    Adam Wrong
    上图中ppt中的方程与原论文不符,正确方法如下图,详见Adam: A Method for Stochastic Optimization。区别有两点:
    1. bias correction的部分,得到的纠正后的m和v只对当前步更新负责,并不会传播到后续循环中,即不更新m和v。
    2. t的更新时间点为循环开始,例如第一次循环的时候t=1。
    Adam

    7. 调整learning rate

    learning rate(学习率)作为上诉所有算法的公有超参数,其大小直接影响模型的结果。如下图所示,合适的学习率既不能过高,又不能过低。
    learning rate
    而且在训练的不同阶段可能需要不同大小的学习率。所以除了采取自适应的优化算法之外,这里介绍一种显式的学习率递降的算法。本质上就是按照三种不同的方式,随着迭代次数的增加,减小学习率。详情如下图所示:
    learning rate decay
    其中的自变量t通常是迭代次数,也可以是iteration或者epoch数。

    展开全文
  • 优化器的功能:管理并更新模型中可学习参数的值,使得模型输出更接近真实标签。 这里的可学习参数一般就是指权值和偏置了,管理指优化器可以修改哪一部分参数。更新就是优化器的更新策略,每个不同的优化器会采取...


    该篇笔记整理自余庭嵩的讲解。如果主要看优化器在pytorch中如何使用则可以直接看optim.SGD这一部分的讲解,该部分对优化器pytorch实现时的各变量解释详细,并且其他优化器的变量与其大同小异,在其他部分里就不再重新说明了。本文重点是探讨其中一种优化器的全部数学原理,其他优化器都是基于不同程度上的改进,最后一部分进行了罗列,如果需要详细了解原理可根据所给论文去阅读。

    基本概念

    优化器的功能:管理更新模型中可学习参数的值,使得模型输出更接近真实标签。
    这里的可学习参数一般就是指权值和偏置了,管理指优化器可以修改哪一部分参数。更新就是优化器的更新策略,每个不同的优化器会采取不同的策略去更新参数的值,这里策略通常是梯度下降。
    在展开讨论之前,先明确下面几个基本概念:

    • 导数:函数在指定坐标轴上的变化率
    • 方向导数:指定方向上的变化率
    • 梯度:一个向量。方向为使得方向导数取得最大值的方向(各个方向上变化率最大的那个方向),模长就是这个最大的变化率。
    • 梯度下降:一种策略。梯度是增长最快的方向,所以取梯度的负方向就是下降最快的那个方向。函数值就是loss值,所以用梯度下降来更新权值,使得函数值最快地降低。

    PyTorch实现与机制

    优化器的属性

    在pytorch中优化器类名定义为Optimizer,其继承于object父类。优化器中包括如下几个主要基本属性:

    • defaults:优化器的超参数。里面通常存储学习率,momentum的值以及decay的值等等
    • state:用于存储参数的缓存。例如momentum会使用到前几次更新时所使用的梯度,那前几次的梯度就需要缓存在state中
    • param_groups:管理的参数组。以list形式存储,list中的每一个元素是一个字典,字典的key(源代码里是’params’)对应的value才是真正的参数,这里的value其实又是一个list,里面包含了多个参数值。前面基本概念中提到了管理的概念,param_groups中存储的参数就是优化器可以修改的全部参数
    • _step_count:记录更新次数,学习率调整中会使用到。例如100轮下降一次学习率,200轮再下降一次学习率,下降了几次就保存在这个属性中

    优化器的方法

    还包含如下几个主要的基本方法(函数):

    • zero_grad():清空所管理参数的梯度。参数都是tensor类型,tensor中会自动存储梯度的信息,而tensor的梯度信息是不会自动清零的。每次反向传播会把梯度值加到tensor的grad里面(累加),因此需要在求导前或者更新后进行清零。该函数判断优化器管理的参数组每个张量的梯度是否是None,如果不是就清零,是就不操作。
    • step():执行一步更新。当计算得loss,再用loss.backward()反向传播计算得到梯度之后,就需要使用step()进行权值参数的更新。更新的策略根据优化器的变化而变化。
    • add_param_group():添加参数组。优化器管理的参数是分组的,对于不同组的参数有不同的超参数的设置。例如NLP文本特征提取部分的参数,我们希望其学习得慢一些,后面自己添加的全连接层,我们希望其学习得快一些,由此就可以分两组参数,对这两组分别设置不同的学习率或者别的超参数,因此需要参数组的概念。
    • state_dict():获取优化器当前状态信息字典。这个函数的返回值是一个字典,字典中只有两个key。一个是state,一个是param_groups。
    • load_state_dict():将状态信息字典加载到优化器当中。这个函数和state_dict()函数使得模型可以断点续训练。比如训练模型需要一个月,如果写了隔几个epoch执行一次state_dict(),那断电也没事,开机找到存好的dict用load加载进来就可以接着前面的进度继续训练了。

    优化器方法使用实例

    首先我们手动创建一个可学习的参数,给其梯度赋值,然后定义好随机梯度下降优化器准备以其为例:

    weight = torch.randn((2, 2), requires_grad=True)
    weight.grad = torch.ones((2, 2))
    
    optimizer = optim.SGD([weight], lr=0.1)
    

    之后我们依次观察优化器的每个方法都做了些什么。

    optimizer.step()

    通过下面代码构造使用实例

    print("weight before step:{}".format(weight.data))
    optimizer.step()        # 修改lr=1 0.1观察结果
    print("weight after step:{}".format(weight.data))
    

    输出结果为

    weight before step:tensor([[0.6614, 0.2669],
            [0.0617, 0.6213]])
    weight after step:tensor([[ 0.5614,  0.1669],
            [-0.0383,  0.5213]])
    

    以第一个值为例,0.6614变成了0.5614,为什么?因为梯度设置的是1,学习率是0.1,所以一次更新1*0.1就等于0.1,由于更新策略定义了是随机梯度下降,所以要在梯度的反方向上更新,所以就在原权值的基础上减去0.1,由此得到最后结果。这里这么简单是因为我们直接赋值好了梯度,实际情况中梯度是要通过求解得出来的。具体的求解机制可粗浅理解入下,矩阵中每一个元素都影响着下一步的几个特定的输出,那矩阵中某个特定元素的梯度其实就是计算其影响的每个输出对其的偏导之和。
    总结一下,step就是利用已经求好的梯度对权值进行一次更新。

    optimizer.zero_grad()

    通过下面代码构造使用实例

    print("weight before step:{}".format(weight.data))
    optimizer.step()
    print("weight after step:{}".format(weight.data))
    print("weight in optimizer:{}\nweight in weight:{}\n".format(id(optimizer.param_groups[0]['params'][0]), id(weight)))
    print("weight.grad is {}\n".format(weight.grad))
    
    optimizer.zero_grad()
    print("after optimizer.zero_grad(), weight.grad is\n{}".format(weight.grad))
    

    输出结果为

    weight before step:tensor([[0.6614, 0.2669],
            [0.0617, 0.6213]])
    weight after step:tensor([[ 0.5614,  0.1669],
            [-0.0383,  0.5213]])
    weight in optimizer:1587976042392
    weight in weight:1587976042392
    
    weight.grad is tensor([[1., 1.],
            [1., 1.]])
    
    after optimizer.zero_grad(), weight.grad is
    tensor([[0., 0.],
            [0., 0.]])
    

    输出结果中值得注意的是,优化器中的权重地址和实际的权重地址是一样的,所以优化器中的权重和实际权重共享内存。

    optimizer.add_param_group()

    通过下面代码构造使用实例,给优化器以字典的形式添加一组参数

    print("optimizer.param_groups is\n{}".format(optimizer.param_groups))
    
    w2 = torch.randn((3, 3), requires_grad=True)
    optimizer.add_param_group({"params": w2, 'lr': 0.0001})
    
    print("optimizer.param_groups is\n{}".format(optimizer.param_groups))
    

    输出结果为

    optimizer.param_groups is
    [{'params': [tensor([[0.6614, 0.2669],
            [0.0617, 0.6213]], requires_grad=True)], 'lr': 0.1, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}]
    optimizer.param_groups is
    [{'params': [tensor([[0.6614, 0.2669],
            [0.0617, 0.6213]], requires_grad=True)], 'lr': 0.1, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}, 
     {'params': [tensor([[-0.4519, -0.1661, -1.5228],
            [ 0.3817, -1.0276, -0.5631],
            [-0.8923, -0.0583, -0.1955]], requires_grad=True)], 'lr': 0.0001, 'momentum': 0, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}]
    

    就可以看见param_group中有两个字典了。

    optimizer.state_dict()

    通过代码构造下面使用实例:

    optimizer = optim.SGD([weight], lr=0.1, momentum=0.9)
    opt_state_dict = optimizer.state_dict()
    
    print("state_dict before step:\n", opt_state_dict)
    
    for i in range(10):
        optimizer.step()
    
    print("state_dict after step:\n", optimizer.state_dict())
    
    torch.save(optimizer.state_dict(), os.path.join(DIR, "optimizer_state_dict.pkl"))
    

    输出结果为

    state_dict before step:
     {'state': {}, 'param_groups': [{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [1636409304984]}]}
    state_dict after step:
     {'state': {1636409304984: {'momentum_buffer': tensor([[6.5132, 6.5132],
            [6.5132, 6.5132]])}}, 'param_groups': [{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [1636409304984]}]}
    

    可见,初次定义state_dict时,里面的state栏还没有信息。进行了十步更新之后,state_dict中就随时存储信息了。之后用torch.save把训练十步之后的优化器信息存在电脑硬盘上,后面就可以随时加载了。

    optimizer.load_state_dict()

    通过代码构造下面使用实例,构建一个优化器,读取进来pkl,之后调用函数:

    optimizer = optim.SGD([weight], lr=0.1, momentum=0.9)
    state_dict = torch.load(os.path.join(BASE_DIR, "optimizer_state_dict.pkl"))
    
    print("state_dict before load state:\n", optimizer.state_dict())
    optimizer.load_state_dict(state_dict)
    print("state_dict after load state:\n", optimizer.state_dict())
    

    输出结果为

    state_dict before load state:
     {'state': {}, 'param_groups': [{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [2225753515928]}]}
    state_dict after load state:
     {'state': {2225753515928: {'momentum_buffer': tensor([[6.5132, 6.5132],
            [6.5132, 6.5132]])}}, 'param_groups': [{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False, 'params': [2225753515928]}]}
    

    可见该函数加载进来了信息,可以接着这个信息进行训练了。

    优化器数学原理

    学习率

    直接通过实例来理解学习率,例如目标函数如下:
    y=4x2y=4x^2
    把这里的x看成是权重,y看成是loss,下面通过代码来理解学习率的作用。
    首先构造这个函数:

    def func(x_t):
        return torch.pow(2*x_t, 2)
    
    x = torch.tensor([2.], requires_grad=True)
    

    然后画出-10到10区间上的均匀分布的500个点的函数图像如下:
    在这里插入图片描述
    接下来模拟一下优化的过程,假如不考虑学习率的概念,直接令其为1即可,迭代四次。

    # 记录loss迭代次数,画曲线
    iter_rec, loss_rec, x_rec = list(), list(), list()
    
    lr = 1
    max_iteration = 4
    
    for i in range(max_iteration):
    
        y = func(x)  # 得出loss值
        y.backward()  # 计算x的梯度
    
        print("Iter:{}, X:{:8}, X.grad:{:8}, loss:{:10}".format(
            i, x.detach().numpy()[0], x.grad.detach().numpy()[0], y.item()))
    
        x_rec.append(x.item())
    
    	# 更新参数
        x.data.sub_(lr * x.grad)    #x = x - x.grad
        x.grad.zero_()
    
        iter_rec.append(i)
        loss_rec.append(y)
    
    plt.subplot(121).plot(iter_rec, loss_rec, '-ro')
    plt.xlabel("Iteration")
    plt.ylabel("Loss value")
    
    x_t = torch.linspace(-3, 3, 100)
    y = func(x_t)
    plt.subplot(122).plot(x_t.numpy(), y.numpy(), label="y = 4*x^2")
    plt.grid()
    y_rec = [func(torch.tensor(i)).item() for i in x_rec]
    plt.subplot(122).plot(x_rec, y_rec, '-ro')
    plt.legend()
    plt.show()
    

    观察loss值(也就是函数值的变化):

    Iter:0, X:     2.0, X.grad:    16.0, loss:      16.0
    Iter:1, X:   -14.0, X.grad:  -112.0, loss:     784.0
    Iter:2, X:    98.0, X.grad:   784.0, loss:   38416.0
    Iter:3, X:  -686.0, X.grad: -5488.0, loss: 1882384.0
    

    可以发现,仅仅迭代四次,loss值就已经涨到了一个及其庞大的数值。说明每一次的更新公式x=x-x.grad是不合理的,因为会更新过头,一旦过头,这个式子只会让x的值更大,而x更大梯度就越大,如此往复就形成了函数值越来越大的情况。
    由此就引入了学习率的概念,一次更新先别更新那么多,只按着梯度的方向更新一小步,那怎么表示沿着梯度的方向更新一小步的概念?由此想到在梯度前面乘以数值对更新的程度进行缩小,这个数值就是学习率,如果令其为0.2,再更新四次,loss值和更新过程的曲线图如下所示:
    在这里插入图片描述
    这就快要更新到那个最优值了,如果再增加迭代次数就变得收敛了,把迭代次数增加至20次,可得:
    在这里插入图片描述
    那如果把学习率设置成0.125呢?这时候就会发现,只需要一步迭代,就直接迭代到了最优点,后面梯度为0,权值就不再更新了。那么由此就产生了疑问,不同的学习率对于优化的过程到底怎样的影响呢?为观察影响,设置不同的学习率来作出对比曲线图,看0.01到0.2上均匀分布的10个学习率对优化过程的影响曲线,代码如下:

    iteration = 100
    num_lr = 10
    lr_min, lr_max = 0.01, 0.2  # .5 .3 .2
    
    lr_list = np.linspace(lr_min, lr_max, num=num_lr).tolist()
    loss_rec = [[] for l in range(len(lr_list))]
    iter_rec = list()
    
    for i, lr in enumerate(lr_list):
        x = torch.tensor([2.], requires_grad=True)
        for iter in range(iteration):
    
            y = func(x)
            y.backward()
            x.data.sub_(lr * x.grad)  # x.data -= x.grad
            x.grad.zero_()
    
            loss_rec[i].append(y.item())
    
    for i, loss_r in enumerate(loss_rec):
        plt.plot(range(len(loss_r)), loss_r, label="LR: {}".format(lr_list[i]))
    plt.legend()
    plt.xlabel('Iterations')
    plt.ylabel('Loss value')
    plt.show()
    

    画出的对比曲线图为:
    在这里插入图片描述
    可见,最小的学习率,收敛的时间也是最慢的,第二小的学习率,收敛时间第二慢。那么令收敛时间最快(也就是迭代次数最少)的学习率是哪一个呢?发现是0.136这条浅紫色的线。为什么?因为它距离0.125这个学习率最近,0.125是一步迭代直接就到最优值的那个学习率。
    但是现实问题中,不会有人告诉你这个曲线的函数表达式让你求出来那个最快最合适的学习率。所以大部分情况只能设置0.01或者更小的学习率慢慢迭代,也可以达到收敛的效果。

    动量

    数学原理

    结合当前梯度与上一次更新信息,用于当前更新。就是让更新的方向具有了惯性。在更加具体了解动量之前,先了解如下概念。
    指数加权平均:一种求取平均值的方法
    vt=βvt1+(1β)θt\mathbf{v}_{t}=\boldsymbol{\beta} * \boldsymbol{v}_{t-1}+(\mathbf{1}-\boldsymbol{\beta}) * \boldsymbol{\theta}_{t}
    这个方法的思想可以这么理解:要求取当前时刻的平均值,距离当前时刻越近的那些值,其参考性越大,其权重也就越大,而这些权重是随着时间间隔的扩大呈指数下降的。
    看表达式内容,vtv_t是当前时刻的值,它由两个部分组成,第一个部分是上一时刻的值,第二个部分是当前时刻的参数值,这两个部分加权求和就得到了当前时刻的平均值,这里权重之和为1。
    举个具体实例说明一下。下图是一个时间序列,横轴是天数,纵轴是当天的温度:
    在这里插入图片描述
    如果我需要求取第100天的时候的温度的平均值该怎么做?这时候就需要用到上面提出的指数加权平均公式了,代入公式如下:
    v100=βv99+(1β)θ100=(1β)θ100+β(βv98+(1β)θ99)=(1β)θ100+(1β)βθ99+(β2v98)=(1β)θ100+(1β)βθ99+(1β)β2θ98+(β3v97)=(1β)θ100+(1β)βθ99+(1β)β2θ98+(β3v97)=iN(1β)βiθNi\begin{aligned} \mathbf{v}_{100}&=\beta * v_{99}+(1-\beta) * \theta_{100} \\ &=(1-\beta) * \theta_{100}+\beta *\left(\beta * v_{98}+(1-\beta) * \theta_{99}\right) \\ &=(1-\beta) * \theta_{100}+(1-\beta) * \beta * \theta_{99}+\left(\beta^{2} * v_{98}\right)\\ &=(\mathbf{1}-\boldsymbol{\beta})*\boldsymbol{\theta}_{\mathbf{1 0 0}}+(\mathbf{1}-\boldsymbol{\beta}) * \boldsymbol{\beta}*\boldsymbol{\theta}_{\mathbf{9 9}}+(\mathbf{1}-\boldsymbol{\beta})*\boldsymbol{\beta}^\mathbf{2}*\boldsymbol{\theta}_\mathbf{98}+\left(\boldsymbol{\beta}^\mathbf{3} * \boldsymbol{v}_\mathbf{97}\right)\\ &=(\mathbf{1}-\boldsymbol{\beta}) * \boldsymbol{\theta}_{\mathbf{1 0 0}}+(\mathbf{1}-\boldsymbol{\beta}) * \beta * \boldsymbol{\theta}_{\mathbf{9 9}}+(\mathbf{1}-\boldsymbol{\beta}) * \boldsymbol{\beta}^{\mathbf{2}} * \boldsymbol{\theta}_{\mathbf{9 8}}+\left(\boldsymbol{\beta}^\mathbf{3} * \boldsymbol{v}_\mathbf{97}\right)\\ &=\sum_{i}^{N}(\mathbf{1}-\boldsymbol{\beta}) * \boldsymbol{\beta}^{i} * \boldsymbol{\theta}_{\boldsymbol{N}-\boldsymbol{i}} \end{aligned}
    这就可以发现,离当前时刻N越远的数值,权重是呈指数形式的,由于β\beta是一个小于1的数值,所以权重是呈指数下降的。那么下降程度到底是怎样的,可通过代码直观感受一下。

    代码实例

    首先构造一个上面公式里的函数,再给beta和N赋上具体数值:

    def exp_w_func(beta, time_list):
    	# 返回多个时间点的权重值
        return [(1 - beta) * np.power(beta, exp) for exp in time_list]
    
    beta = 0.9
    num_point = 100
    time_list = np.arange(num_point).tolist()  # 100个时间点
    

    接下来画出权重变化的图像:

    weights = exp_w_func(beta, time_list)
    
    plt.plot(time_list, weights, '-ro', label="Beta: {}\ny = B^t * (1-B)".format(beta))
    plt.xlabel("time")
    plt.ylabel("weight")
    plt.legend()
    plt.title("exponentially weighted average")
    plt.show()
    
    print(np.sum(weights))
    

    得到图像
    在这里插入图片描述
    可见,距离当前时刻越远,对于当前时刻平均值的影响就越小,越近,影响越大。这就是指数加权平均的思想。指数加权平均最核心的就是超参数β\beta,那么取不同值的时候权重是如何衰减的呢?
    画出超参数β\beta分别为0.98,0.95,0.9,0.8时的对比图:

    beta_list = [0.98, 0.95, 0.9, 0.8]
    w_list = [exp_w_func(beta, time_list) for beta in beta_list]
    for i, w in enumerate(w_list):
        plt.plot(time_list, w, label="Beta: {}".format(beta_list[i]))
        plt.xlabel("time")
        plt.ylabel("weight")
    plt.legend()
    plt.show()
    

    结果图为:
    在这里插入图片描述
    通过这张图,对β\beta就可以理解为记忆周期的概念了,β\beta越小,其记忆周期越短,β\beta值越大,记忆周期越长。通常β\beta设置为0.9。因为更关注当前天数十天左右的数据。这个10是怎么来的呢?是根据以下公式得到的
    length=11βlength=\frac{1}{1-\beta}
    那么为什么是这个公式呢?个人理解这就是当n很大的时候的等比数列求和公式,所有权值的之和会等于10,也就是综合考虑长度相当于10倍于当前天数的天数(10天)来给当前天数取值。

    动量给SGD带来的改变

    上面详细讨论的参数β\beta在pytorch的优化器中就是参数m也就是momentum系数。以随机梯度下降法为例,动量概念的引入到底可以给随机梯度下降法带来怎样的改变和优化呢?之前梯度下降更新权重的公式如下:
    wi+1=wilrg(wi)\boldsymbol{w}_{\boldsymbol{i}+\mathbf{1}}=\boldsymbol{w}_{\boldsymbol{i}}-\boldsymbol{l} \boldsymbol{r} * \boldsymbol{g}\left(\boldsymbol{w}_{\boldsymbol{i}}\right)
    其中,g是梯度。引入动量之后,pytorch中对权重的更新公式如下:
    vi=mvi1+g(wi)wi+1=wiIrvi\begin{aligned} \boldsymbol{v}_{\boldsymbol{i}} =\boldsymbol{m} * \boldsymbol{v}_{\boldsymbol{i}-\mathbf{1}}+\boldsymbol{g}\left(\boldsymbol{w}_{\boldsymbol{i}}\right) \\ \boldsymbol{w}_{\boldsymbol{i}+\boldsymbol{1}} =\boldsymbol{w}_{\boldsymbol{i}}-\boldsymbol{I} \boldsymbol{r} * \boldsymbol{v}_{\boldsymbol{i}} \end{aligned}
    其中v是更新量,m是momentum系数也就是参数β\beta。这里可以发现,不同之处在于更新权重时用v替换了梯度,而v,也就是更新量的大小和当前梯度以及之前的梯度都有关联。
    还是举天数和气温的例子,第100天温度的平均值为:
    v100=mv99+g(w100)=g(w100)+m(mv98+g(w99))=g(w100)+mg(w99)+m2v98=g(w100)+mg(w99)+m2g(w98)+m3v97\begin{aligned} \boldsymbol{v}_{\mathbf{1 0 0}} &=\boldsymbol{m} * \boldsymbol{v}_{\mathbf{9 9}}+\boldsymbol{g}\left(\boldsymbol{w}_{\mathbf{1 0 0}}\right) \\ &=\boldsymbol{g}\left(\boldsymbol{w}_{\mathbf{1 0 0}}\right)+\boldsymbol{m} *\left(\boldsymbol{m} * \boldsymbol{v}_{\mathbf{9 8}}+\boldsymbol{g}\left(\boldsymbol{w}_{\mathbf{9 9}}\right)\right) \\ &=\boldsymbol{g}\left(\boldsymbol{w}_{\mathbf{1 0 0}}\right)+\boldsymbol{m} * \boldsymbol{g}\left(\boldsymbol{w}_{\mathbf{9 9}}\right)+\boldsymbol{m}^{2} * \boldsymbol{v}_{\mathbf{9 8}} \\ &=\boldsymbol{g}\left(\boldsymbol{w}_{\mathbf{1 0 0}}\right)+\boldsymbol{m} * \boldsymbol{g}\left(\boldsymbol{w}_{\mathbf{9 9}}\right)+\boldsymbol{m}^{2} * \boldsymbol{g}\left(\boldsymbol{w}_{\mathbf{9 8}}\right)+\boldsymbol{m}^{3} * \boldsymbol{v}_{\mathbf{9} \mathbf{7}} \end{aligned}

    代码实例

    对比一下有动量的SGD和没有动量的SGD的优化曲线,来产生直观的感受。代码如下:

    def func(x):
        return torch.pow(2*x, 2)
    
    iteration = 100
    m = 0.
    
    lr_list = [0.01, 0.03]
    
    momentum_list = list()
    loss_rec = [[] for l in range(len(lr_list))]
    iter_rec = list()
    
    for i, lr in enumerate(lr_list):
        x = torch.tensor([2.], requires_grad=True)
    
        momentum = 0. if lr == 0.03 else m
        momentum_list.append(momentum)
    
        optimizer = optim.SGD([x], lr=lr, momentum=momentum)
    
        for iter in range(iteration):
    
            y = func(x)
            y.backward()
    
            optimizer.step()
            optimizer.zero_grad()
    
            loss_rec[i].append(y.item())
    
    for i, loss_r in enumerate(loss_rec):
        plt.plot(range(len(loss_r)), loss_r, label="LR: {} M:{}".format(lr_list[i], momentum_list[i]))
    plt.legend()
    plt.xlabel('Iterations')
    plt.ylabel('Loss value')
    plt.show()
    

    先看都没有动量时候的loss值关于迭代次数的收敛曲线:
    在这里插入图片描述
    再看看给0.01学习率情况添加动量后的对比曲线:
    在这里插入图片描述
    从上图可以看见,添加了momentum的学习率为0.01时的SGD确实比没有momentum时候学习率为0.03时的SGD更快达到了最优值点。但是由于momentum设置得太大了,在到达最优值点的时候,算法还考虑了之前时刻的较大的梯度,所以虽然当前时刻梯度已经为0,但是更新量要考虑之前时刻的大梯度所以不为0,于是在最优点还进行了更新,误差就上去了。
    那看看合适的momentum会是什么样的:
    在这里插入图片描述

    PyTorch中的十种优化器

    optim.SGD

    随机梯度下降法,其具体原理可参考论文《On the Importance of Initialization and Momentum in Deep Learning》
    主要参数:

    • params:管理的参数组
    • lr:初始学习率
    • momentum:动量系数,也就是上面动量小节里面的β\beta
    • weight_decay:L2正则化系数的权重系数
    • nesterov:是否采用NAG(参考文献《On the Importance of Initialization and Momentum in Deep Learning》),默认是false,一般不采用

    optim.Adagrad

    对于每一个学习参数有自适应的学习率,采用的还是梯度下降法,其具体原理可参考论文《Adaptive Subgradient Methods for Online Learning and Stochastic Optimization》

    optim.RMSprop

    Adagrad的改进,其具体原理可参考Hinton讲座的PPT:Neural Networks for Machine Learning

    optim.Adadelta

    Adagrad 的改进,其具体原理可参考论文《An Adaptive Learning Rate Method》

    optim.Adam

    RMSprop结合Momentum,其具体原理可参考论文《Adam: A Method for Stochastic Optimization》

    optim.Adamax

    Adam增加学习率上限,其具体原理可参考论文《Adam: A Method for Stochastic Optimization》

    optim.SparseAdam

    稀疏版的Adam

    optim.ASGD

    随机平均(Average)梯度下降,其具体原理可参考论文《Accelerating Stochastic Gradient Descent using Predictive Variance
    Reduction》。

    optim.Rprop

    弹性反向传播,这个优化器的应用场景通常在用所有的样本一起计算梯度的情况下,也就是batchsize等于样本数的时候。这个情况叫做full batch,现在采用的都是mini batch。其具体原理可参考论文《Martin Riedmiller und Heinrich Braun》。

    optim.LBFGS

    BFGS的改进,L代表limited memory。是对BFGS在内存消耗上的一个改进。

    展开全文
  • 基于梯度的权重更新优化迭代算法

    千次阅读 2017-03-20 18:44:37
    有时间参数 tt 参与的一般都为迭代式的算法 gt 表示当前时刻的梯度; γ,β:常数;
  • 我想,大家一看便知,哪个是优化之前的代码,哪个不是。 迭代本身非常有用,非常有意思,便于理解。还有一种方法,既有迭代优良的特性,能够找到最优解,而且性能也非常得好的方法。这个方法是动态规划方法。但是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,160
精华内容 4,464
关键字:

优化迭代