拟牛顿法:python代码实现

news2025/6/19 8:03:11

文章目录

  • 拟牛顿法
  • 待优化实例
  • scipy工具包实现BFGS
  • 自编Python实现BFGS

拟牛顿法

在梯度类算法原理:最速下降法、牛顿法和拟牛顿法中,介绍了梯度类算法求解优化问题的设计思路,并以最速下降法、牛顿法和拟牛顿法为例,描述了具体的算法迭代过程。其中,拟牛顿法(Broyden–Fletcher–Goldfarb–Shanno,BFGS)在实际优化场景中被广泛使用,因此本文将自主编写Python代码,实现BFGS的全部过程,并且和现有工具包做对比,从而加深对BFGS的理解。

待优化实例

本文使用的待优化实例为10维的Rosenbrock函数:
f ( x ) = ∑ i = 2 10 [ 100 ( x i − x i − 1 2 ) 2 + ( 1 − x i − 1 ) 2 ] f(x)=\sum_{i=2}^{10} [100(x_i-x_{i-1}^2)^2+(1-x_{i-1})^2] f(x)=i=210[100(xixi12)2+(1xi1)2]
10维图像难以可视化,此处编写代码绘制2维函数的等高线图,更直观地认识该函数。

import numpy as np
import matplotlib.pyplot as plt


# 任意维度Rosenbrock函数
def rosen(x):
    return sum(100 * (x[1:]-x[:-1] ** 2) ** 2 + (1 - x[:-1]) ** 2)


# 绘制等高线图
def plot_contour(f):

    # x和y区间均为[0, 2]
    x = np.linspace(0, 2, 100)
    y = np.linspace(0, 2, 100)

    # 将原始数据变成网格数据形式
    X, Y = np.meshgrid(x, y)

    # 计算对应目标函数值
    Z = np.zeros_like(X)
    for i in range(Z.shape[0]):
        for j in range(Z.shape[1]):
            Z[i, j] = f(np.array([X[i, j], Y[i, j]]))

    # 设置打开画布大小,长10,宽6
    plt.figure(figsize=(10, 6))
    # 画等高线
    cset = plt.contourf(X, Y, Z, 6, cmap=plt.cm.hot)  # 颜色分层,6层
    contour = plt.contour(X, Y, Z, 8, colors='k')  # 绘制8条等高线
    plt.clabel(contour, fontsize=10, colors='k')  # 等高线上标明Z值
    plt.colorbar(cset)  # 显示颜色条
    plt.scatter(1, 1, marker='p')  # 标明最优解位置
    plt.title('2D-Rosenbrock contour')  # 增加标题
    plt.show()


if __name__ == '__main__':

    plot_contour(rosen)

以下为2维Rosenbrock的等高线图。其大致呈抛物线形,最小值位于抛物线形的山谷中(香蕉型山谷,图中暗红色区域,最小值位置 [ 1 , 1 ] [1,1] [1,1])。这个山谷通常很容易被找到,但由于山谷内的值变化不大,要找到最小值比较困难。

scipy工具包实现BFGS

python的scipy.optimize工具包能够实现BFGS,具体可以参考scipy.optimize优化器的各种使用。本节仅做简要描述。

主要的求解方案有两种:第一种是只将待优化函数和初值传递给工具包,其余内容全部由工具包自行计算;第二种是将待优化函数的雅可比矩阵也传递给工具包,提升求解的速度。

雅可比矩阵之前没提到过,这里解释一下。雅可比矩阵是一阶偏导数以一定方式排列成的矩阵。假设 f = [ f 1 , f 2 , ⋅ ⋅ ⋅ , f m ] \pmb f=[f_1,f_2,···,f_m] f=[f1,f2,⋅⋅⋅,fm] x = [ x 1 , x 2 , ⋅ ⋅ ⋅ , x n ] \pmb x=[x_1,x_2,···,x_n] x=[x1,x2,⋅⋅⋅,xn],那么 f \pmb f f的雅可比矩阵 J \pmb J J m × n m×n m×n 的矩阵:
J = [ ∂ f ∂ x 1 ⋅ ⋅ ⋅ ∂ f ∂ x n ] = [ ∂ f 1 ∂ x 1 ⋯ ∂ f 1 ∂ x n ⋮ ⋱ ⋮ ∂ f m ∂ x 1 ⋯ ∂ f m ∂ x n ] \pmb J=[\frac{\partial \pmb f}{\partial x_1}···\frac{\partial \pmb f}{\partial x_n}]=\left[ \begin{matrix} \frac{\partial f_1}{\partial x_1} & \cdots & \frac{\partial f_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial f_m}{\partial x_1} & \cdots & \frac{\partial f_m}{\partial x_n} \\ \end{matrix} \right] J=[x1f⋅⋅⋅xnf]= x1f1x1fmxnf1xnfm

针对Rosenbrock函数,一阶偏导数为

∂ f ∂ x j = 200 ( x j − x j − 1 2 ) − 400 x j ( x j + 1 − x j 2 ) − 2 ( 1 − x j ) \frac{\partial f}{\partial x_j} =200(x_j-x_{j-1}^2)-400x_j(x_{j+1}-x_j^2)-2(1-x_j) xjf=200(xjxj12)400xj(xj+1xj2)2(1xj)
j = 1 j=1 j=1
∂ f ∂ x 1 = − 400 x 1 ( x 2 − x 1 2 ) − 2 ( 1 − x 1 ) \frac{\partial f}{\partial x_1}=-400x_1(x_2-x_1^2)-2(1-x_1) x1f=400x1(x2x12)2(1x1)
j = 10 j=10 j=10
∂ f ∂ x 10 = 200 ( x 10 − x 9 2 ) \frac{\partial f}{\partial x_{10}}=200(x_{10}-x_9^2) x10f=200(x10x92)

两种求解方案的代码如下:

import numpy as np
from scipy.optimize import minimize
import time


def rosen(x):
    return sum(100 * (x[1:]-x[:-1] ** 2) ** 2 + (1 - x[:-1]) ** 2)


def rosen_der(x):

    xm = x[1:-1]
    xm_m1 = x[:-2]
    xm_p1 = x[2:]
    der = np.zeros_like(x)
    der[1:-1] = 200 * (xm - xm_m1 ** 2) - 400 * (xm_p1 - xm ** 2) * xm - 2 * (1 - xm)
    der[0] = -400 * x[0] * (x[1] -x[0] ** 2) - 2 * (1 - x[0])
    der[-1] = 200 * (x[-1] - x[-2] ** 2)

    return der


if __name__ == '__main__':

    np.random.seed(0)  # 设置种子,保证结果可重复
    x0 = np.random.rand(10)  # 随机生成初始值
    print('------------不提供jac计算--------------')
    res = minimize(rosen, x0, options={'disp': True})  # 不提供jac
    print('-------------提供jac计算---------------')
    res1 = minimize(rosen, x0, jac=rosen_jac, options={'disp': True})  # 提供jac

    # 不提供jac,统计计算时间
    start_time = time.time()
    for i in range(200):
        res = minimize(rosen, x0, options={'disp': False})
    end_time = time.time()
    calc_time_non_jac = end_time - start_time

    # 提供jac,统计计算时间
    start_time = time.time()
    for i in range(200):
        res = minimize(rosen, x0, jac=rosen_jac, options={'disp': False})
    end_time = time.time()
    time1 = end_time - start_time
    calc_time_with_jac = end_time - start_time

    # 对比计算时间
    print('-------------评估jac效率提升---------------')
    print('不提供jac时,计算时间为:{} 秒; 提供jac后,计算时间为:{} 秒,时间降低为原来的:{}'.format(
        calc_time_non_jac, calc_time_with_jac, calc_time_with_jac / calc_time_non_jac))

输出结果如下。两个方案都可以找到最优解;当不提供雅可比矩阵时,目标函数计算了572次,提供雅可比矩阵后,目标函数的计算次数降至52次;具体到计算效率上,提供雅可比矩阵可以将计算时间降至原先的30%。

------------不提供jac计算--------------
Optimization terminated successfully.
         Current function value: 0.000000
         Iterations: 41
         Function evaluations: 572
         Gradient evaluations: 52
-------------提供jac计算---------------
Optimization terminated successfully.
         Current function value: 0.000000
         Iterations: 42
         Function evaluations: 52
         Gradient evaluations: 52
-------------评估jac效率提升---------------
不提供jac时,计算时间为:3.727977991104126; 提供jac后,计算时间为:1.1221048831939697 秒,时间降低为原来的:0.3009955761197058

自编Python实现BFGS

自行编写代码时,主要关注三项内容:

(1) 迭代公式: x k + 1 = x k + α ∗ G ∗ g x_{k+1} = x_k + \alpha * G * g xk+1=xk+αGg。此处, α \alpha α是迭代步长, G G G是替代 H − 1 H^{-1} H1的函数, g g g是一阶导数。

(2) α \alpha α计算:代码中(calc_alpha_by_ArmijoRule函数)使用的是线搜索技术·Armijo准则。该技术此前未提过,此处先给自己挖个坑,后续找时间再补上。

(3) G G G计算:代码中(update_D_by_BFGS函数)使用的是和梯度类算法原理:最速下降法、牛顿法和拟牛顿法中完全一致的过程。

import numpy as np


def rosen(x):
    return sum(100 * (x[1:] - x[:-1] ** 2) ** 2 + (1 - x[:-1]) ** 2)


def rosen_jac(x):
    xm = x[1:-1]
    xm_m1 = x[:-2]
    xm_p1 = x[2:]
    der = np.zeros_like(x)
    der[1:-1] = 200 * (xm - xm_m1 ** 2) - 400 * (xm_p1 - xm ** 2) * xm - 2 * (1 - xm)
    der[0] = -400 * x[0] * (x[1] - x[0] ** 2) - 2 * (1 - x[0])
    der[-1] = 200 * (x[-1] - x[-2] ** 2)

    return der


def calc_alpha_by_ArmijoRule(xCurr, fCurr, gCurr, dCurr, c=1.e-4, v=0.5):
    i = 0
    alpha = v ** i
    xNext = xCurr + alpha * dCurr
    fNext = rosen(xNext)

    while True:
        if fNext <= fCurr + c * alpha * np.matmul(dCurr.T, gCurr):
            break
        i += 1
        alpha = v ** i
        xNext = xCurr + alpha * dCurr
        fNext = rosen(xNext)

    return alpha


def update_D_by_BFGS(xCurr, gCurr, xNext, gNext, GCurr):

    sk = xNext - xCurr
    yk = gNext - gCurr
    term1 = GCurr @ yk @ yk.T @ GCurr.T / (yk.T @ GCurr.T @ yk)
    term2 = sk @ sk.T / (sk.T @ yk)
    Dk = GCurr - term1 + term2

    return Dk


def calc_by_self(initial_x):

    # x:变量, f:目标函数, g:目标函数一阶导数, D:H^(-1)的替代表达式
    xCurr = initial_x.reshape((len(initial_x), 1))
    fCurr = rosen(xCurr)
    gCurr = rosen_jac(xCurr).reshape((len(xCurr), 1))
    GCurr = np.identity(len(xCurr))
    iterations = 0

    # 一阶导数不为0,则持续迭代
    while np.linalg.norm(gCurr) > 1e-6:

        # 迭代方向:GCurr * gCurr
        dCurr = -np.matmul(GCurr, gCurr)
        # 迭代步长策略:ArmijoRule
        alpha = calc_alpha_by_ArmijoRule(xCurr, fCurr, gCurr, dCurr)

        # 基本迭代公式:x_{k+1} = x_k + alpha * GCurr * gCurr
        xNext = xCurr + alpha * dCurr
        fNext = rosen(xNext)
        gNext = rosen_jac(xNext)

        # BFGS更新G
        GNext = update_D_by_BFGS(xCurr, gCurr, xNext, gNext, GCurr)

        xCurr, fCurr, gCurr, GCurr = xNext, fNext, gNext, GNext
        iterations += 1

    print('iterations: {},  best_f: {}'.format(iterations, fCurr))


if __name__ == '__main__':
    np.random.seed(0)  # 设置种子,保证结果可重复
    x0 = np.random.rand(10)  # 随机生成初始值

    # 自编代码实现
    calc_by_self(x0)

运行代码后,可以得到如下结果。显然,自编代码的最优解和使用scipy工具包得到的结果是一致的。

iterations: 207,  best_f: [6.79531171e-18]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/411680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3.9、互斥锁(互斥量)

3.9、互斥锁&#xff08;互斥量&#xff09;1.互斥锁&#xff08;互斥量&#xff09;的介绍2. 互斥量相关操作函数3.互斥量函数的使用介绍①pthread_mutex_init②pthread_mutex_destroy③pthread_mutex_lock④pthread_mutex_trylock⑤pthread_mutex_unlock3.利用互斥锁实现线程…

计讯物联双网口工业路由器TR321助力货轮冷链监测解决方案高质量落地

政策背景 国务院办公厅印发我国冷链物联流域第一份五年规划——《“十四五”冷链物流发展规划》&#xff08;以下简称“规划”&#xff09;。《规划》聚焦冷链物流体系、冷链运输、冷链物流服务、冷链物流创新及冷链物流监管体系等方面&#xff0c;对冷链物流的全流程、全环节…

Ficow 的 AI 平台快速上手指南(ChatGPT, NewBing, ChatGLM-6B, cursor.so)

本文首发于 Ficow Shen’s Blog&#xff0c;原文地址&#xff1a; Ficow 的 AI 平台快速上手指南(ChatGPT, NewBing, ChatGLM-6B, cursor.so)。 内容概览 前言OpenAI —— ChatGPT微软 —— NewBing智谱AI —— ChatGLM-6BAI生成代码 —— cursor.so总结 前言 现在各种AI工具大…

虚拟机网络配置

点击【编辑虚拟机设置】&#xff0c;点击【网络适配器】&#xff0c;选择【桥接模式】 选择好之后退回主页&#xff0c;点击【编辑】&#xff0c;选择【虚拟网络编辑器】 添加一个【VMnet8】的网络名称 点击【开启虚拟机】 输入账户密码&#xff0c;输入【cd /etc/sysconfig/ne…

springcloud——gateway功能拓展

目录 1.获取用户真实IP 2.统一跨域配置 3.redis令牌桶算法限流 1.获取用户真实IP 在我们的日常业务中&#xff0c;我们时常需要获取用户的IP地址&#xff0c;作登录日志、访问限制等相关操作。 而在我们的开发架构中&#xff0c;一般我们将服务分为多个微服务&#xff0c;…

熟练了Flex布局之后,该学学Grid布局了

介绍 CSS Gird布局也叫二维网格布局系统&#xff0c;可用于布局页面主要的区域布局或小型组件。网格是一组相交的水平线和垂直线&#xff0c;它定义了网格的列和行。我们可以指定将网格元素放置在与这些行和列相关的位置上。 一维布局 和 二维布局 像流布局和Flex布局&#…

Windows10系统安装perl命令

文章目录1&#xff0c;下载ActivePerl 5.28&#xff08;基于Windows 10系统&#xff09;&#xff1a;1.1&#xff0c;Perl 主页: https://www.perl.org/get.html1.2&#xff0c;选择windows1.3&#xff0c;选择Binaries---activeperla版本1.3&#xff0c;直接选择windows 5.36版…

【观察】神州数码高质量发展背后,是技术创新“叠加效应”的释放

毫无疑问&#xff0c;在百年变局和世纪疫情的双重影响下&#xff0c;整个2022年科技行业的增速都在放缓&#xff0c;更对身处其中的科技企业的业务连续性和成长性提出了更高的考验。尽管如此&#xff0c;神州数码&#xff08;000034.SZ&#xff09;仍然交出了一份令市场亮眼的成…

【iOS逆向与安全】使用ollvm混淆你的源码

前言 当你在研究别人源码的时候&#xff0c;是不是期望着别人代码没有进行任何的防护和混淆。这时的你&#xff0c;是不是应该考虑一下自己代码的安全.本篇文章将告诉你&#xff0c;如何使用ollvm来混淆iOS端的代码【此文为入门贴&#xff0c;大佬请绕道】。 一、目标 编译o…

【MybatisPlus快速入门】—— 拓展入门

逻辑删除 前面我们完成了基本的增删改查操作&#xff0c;但是对于删除操作来说&#xff0c;我们思考一个问题&#xff0c;在实际开发中我们真的会将数据完成从数据库中删除掉么&#xff1f;很多情况下我们是需要保留要删除的数据用来总结统计的&#xff0c;所以我们是不能将数…

从零学习SDK(5)SDK文档的学习和参考

要想充分利用SDK的优势&#xff0c;仅仅下载和安装SDK是不够的&#xff0c;还需要学习和参考SDK提供的文档和资源。文档和资源是SDK的重要组成部分&#xff0c;它们可以帮助开发者掌握SDK的基本概念、结构、用法、限制和最佳实践&#xff0c;以及解决常见的问题和错误。 查找…

(数字图像处理MATLAB+Python)第三章图像基本运算-第二节:图像代数运算

文章目录一&#xff1a;图像算数运算&#xff08;1&#xff09;加法运算A&#xff1a;概述B&#xff1a;程序&#xff08;2&#xff09;减法运算A&#xff1a;概述B&#xff1a;程序&#xff08;3&#xff09;乘法运算A&#xff1a;概述B&#xff1a;程序&#xff08;4&#xf…

C++模板基础(九)

完美转发与 lambda 表达式模板 void f(int& input) {std::cout << "void f(int& input)\t" << input << \n; }void f(int&& input) {std::cout << "void f(int&& input)\t" << input << \n;…

MYSQL8窗口函数

MYSQL8窗口函数MYSQL8窗口函数窗口函数分类序号函数--排行榜row_number()示例rank()示例dense_rank()示例partition by对每个分区内的行进行排名不加partition by全局排序开窗聚合函数分布函数CUME_DIST()PERCENT_RANK()前后函数LAG()的用法LEAD()头尾函数其他函数NTH_VALUE()N…

用Abp实现两步验证(Two-Factor Authentication,2FA)登录(二):Vue网页端开发

文章目录发送验证码登录退出登录界面控件获取用户信息功能项目地址前端代码的框架采用vue.js elementUI 这套较为简单的方式实现&#xff0c;以及typescript语法更方便阅读。首先添加全局对象&#xff1a; loginForm: 登录表单对象 twoFactorData: 两步验证数据&#xff0c; …

跨平台应用开发进阶(五十九):uni-app实现视屏播放小窗功能

文章目录一、前言二、解决方案三、拓展阅读一、前言 在业务功能开发过程中&#xff0c;需要实现视频直播、播放小窗功能。鉴于目前通过接入火山webSDK实现视频直播、点播功能。需要火山协助配合改造实现小窗功能。 uni-app插件市场也提供了若干插件&#xff0c;经试用效果并不…

从二叉树的角度看快速排序

快速排序本质上可以看作二叉树的前序遍历 快速排序是先将一个元素排好序&#xff0c;然后再将剩下的元素排好序 核心思路依然是分治 快排整体思路 准确的可以说是治分 > 先治 得到分界点后 再分 治&#xff1a;双指针技巧&#xff08;左右指针或者快慢指针&#xff0c;…

【Docker】11、IDEA集成Docker插件实现一键部署SpringBoot项目

日常开发项目的过程中&#xff0c;我们每次需要部署线上的时候&#xff0c;都需要安装一大堆的运行环境&#xff0c;例如&#xff1a;JDK、MySQL、Redis 等&#xff0c;非常花费时间、我们可以使用 Docker 的容器技术&#xff0c;方便快捷地搭建项目启动所需要的运行环境&#…

【微服务笔记15】微服务组件之Config配置中心实现用户认证、配置属性加解密

这篇文章&#xff0c;主要介绍微服务组件之Config配置中心实现用户认证、配置属性加解密。 目录 一、用户认证 1.1、引入security依赖 1.2、服务端ConfigServer添加访问配置 1.3、客户端ConfigClient添加访问配置 二、配置属性加解密 2.1、对称加密 &#xff08;1&#…

逍遥自在学C语言 | 位运算符^的高级用法

前言 在上一篇文章中&#xff0c;我们介绍了|运算符的高级用法&#xff0c;本篇文章&#xff0c;我们将介绍^ 运算符的一些高级用法。 一、人物简介 第一位闪亮登场&#xff0c;有请今后会一直教我们C语言的老师 —— 自在。 第二位上场的是和我们一起学习的小白程序猿 —— …