快捷键

torch.optim.lbfgs 的源代码

# mypy: 允许未类型化定义
from 打字 导入 可选, 联合

导入 火炬
from 火炬 导入 张量

from .优化器 导入 优化器, ParamsT


全部 = ["LBFGS"]


定义 _cubic_interpolate(x1, f1, g1, x2, f2, g2, 边界=):
    来自 https://github.com/torch/optim/blob/master/polyinterp.lua 的移植
    计算插值区域的边界
    如果 边界 is  :
        xmin_bound, xmax_bound = bounds
    else:
        xmin_bound, xmax_bound = (x1, x2) 如果 x1 <= x2 否则 (x2, x1)

    # 代码用于最常见情况:两点间的三次插值
    #   提供两个点的函数和导数值
    # 解答在这种情况下(x2 是最远的点):
    #   d1 = g1 + g2 - 3*(f1-f2)/(x1-x2);
    #   d2 = sqrt(d1^2 - g1*g2);
    #   min_pos = x2 - (x2 - x1)*((g2 + d2 - d1)/(g2 - g1 + 2*d2));
    t_new = min(max(min_pos,xmin_bound),xmax_bound);
    d1 = g1 + g2 - 3 * (f1 - f2) / (x1 - x2)
    d2_square = d1**2 - g1 * g2
    如果 d2_平方 >= 0:
        d2 = d2_square.平方根()
        如果 x1 <= x2:
            min_pos = x2 - (x2 - x1) * ((g2 + d2 - d1) / (g2 - g1 + 2 * d2))
        else:
            最小位置 = x1 - (x1 - x2) * ((g1 + d2 - d1) / (g1 - g2 + 2 * d2))
        返回 最小(最大值(最小位置, xmin 边界), xmax 边界)
    else:
        返回 (xmin 边界 + xmax 边界) / 2.0


定义 强沃尔夫(
    目标函数, x, t, d, f, g, gtd, c1=0.0001, c2=0.9, 容差变化=1e-9, 最大 ls=25
):
    # 从 https://github.com/torch/optim/blob/master/lswolfe.lua 端口移植
    d_范数 = d.绝对值().最大值()
    g = g.克隆(内存格式=火炬.连续格式)
    使用初始步评估目标函数和梯度
    f_新, g_新 = 对象函数(x, t, d)
    列表函数评估 = 1
    新 GTD = 新 G.(d)

    包围满足 Wolfe 准则的点的区间
    t_prev, f_prev, g_prev, gtd_prev = 0, f, g, gtd
    完成 = 
    ls_iter = 0
     ls_iter < max_ls:
        # 检查条件
        如果 f_new > (f + c1 * t * gtd) 或者 (ls_iter > 1  f_new >= f_prev):
            括号 = [t_前一个, t]
            括号_f = [f_前一个, 新的]
            括号_g = [g_prev, g_new.克隆(内存格式=火炬.连续格式)]
            bracket_gtd = [gtd_prev, gtd_new]
            断开

        如果 绝对值(gtd_new) <= -c2 * gtd:
            括号 = [t]
            括号_f = [f_new]
            括号_g = [新_g]
            完成 = 真实
            断开

        如果 新_gtd >= 0:
            括号 = [t_上一条, t]
            括号_f = [f_prev, f_new]
            括号_g = [g_prev, g_new.克隆(内存格式=火炬.连续格式)]
            bracket_gtd = [gtd_prev, gtd_new]
            断开

        # interpolate
        最小步长 = t + 0.01 * (t - t_前)
        最大步长 = t * 10
        临时 = t
        t = _三次插值(
            t 前一个, f 前一个, gtd_prev, t, f_new, gtd_new, 边界=(min_step, 最大步长)
        )

        下一步
        t 前 = 临时
        f 前 = 新的
        上一个 = 新的.克隆(内存格式=火炬.连续格式)
        上一个待办事项 = gtd_new
        f_new, g_new = obj_func(x, t, d)
        ls_func_evals += 1
        gtd_new = g_new.(d)
        ls_iter += 1

    达到最大迭代次数了吗?
    如果 ls_iter == 最大 ls:
        括号 = [0, t]
        括号_f = [f, f_新]
        括号_g = [g, g_新]

    # 缩放阶段:我们现在有一个满足条件的点,或者
    # 它周围有一个括号。我们细化这个括号,直到找到
    # 满足条件的精确点
    # 进度不足 = 
    找到括号中的高点和低点
    低点位置, 高点位置 = (0, 1) 如果 括号函数[0] <= 括号_f[-1] 否则 (1, 0)  # type: ignore[possibly-undefined]
      完成  ls_iter < max_ls:
        # 线搜索括号太小
        如果 绝对值(括号[1] - 括号[0]) * d_范数 < 容差变化:  # type: ignore[possibly-undefined]
            断开

        # 计算新试验值
        t = _三次插值(
            括号[0]
            括号_f[0]
            括号_gtd[0]  # type: ignore[possibly-undefined]
            括号[1]
            括号_f[1]
            括号_gtd[1]
        )

        # 测试我们是否在取得足够的进展:
        # 如果`t`离边界太近,我们标记我们正在取得
        进度不足,并且如果
        #   + 上一步我们进展不足,或
        #   + `t` 位于边界之一
        我们将把`t`移动到`0.1 * len(bracket)`的位置
        远离最近的边界点。
        eps = 0.1 * (最大值(括号) - 最小(括号))
        如果 最小(最大值(括号) - t, t - 最小(括号)) < eps:
            # 边界附近的插值
            如果 进度不足 或者 t >= 最大值(括号) 或者 t <= 最小(括号):
                在边界外 0.1 处评估
                如果 绝对值(t - 最大值(括号)) < 绝对值(t - 最小(括号)):
                    t = 最大值(括号) - eps
                else:
                    t = 最小(括号) + eps
                进度不足 = 
            else:
                进度不足 = 真实
        else:
            进度不足 = 

        评估新点
        新_f, 新_g = 对象函数(x, t, d)
        ls_func_evals += 1
        gtd_new = g_new.(d)
        ls_iter += 1

        如果  > (f + c1 * t * gtd) 或者  >= 括号_f[低位置]:
            # Armijo 条件不满足或低于最低点
            括号[高位] = t
            括号_f[高位] = 新_f
            括号_g[高位] = g_新.克隆(内存格式=火炬.连续格式)  # type: ignore[possibly-undefined]
            括号_gtd[高位] = GTD 新
            低位, 高位 = (0, 1) 如果 括号_f[0] <= 括号_f[1] 否则 (1, 0)
        else:
            如果 绝对值(gtd_new) <= -c2 * gtd:
                狼群条件满足
                完成 = 真实
            如果...否则 gtd_new * (括号[高位] - 括号[低位]) >= 0:
                旧高变新低
                括号[高位] = 括号[低位置]
                括号_f[高位置] = 括号_f[低位置]
                括号_g[高位置] = 括号_g[低频词]  # type: ignore[possibly-undefined]
                括号内目标词[高频词] = 括号_gtd[低位]

            # 新的低点成为新低
            括号[低频词] = t
            方括号 f[低频词] = 新 f
            括号_g[低位] = g_新.克隆(内存格式=火炬.连续格式)  # type: ignore[possibly-undefined]
            括号_gtd[低位] = gtd_new

    # 返回物品
    t = 括号[低位置]  # type: ignore[possibly-undefined]
    新_f = 括号_f[低位]
    g_new = 括号_g[低位置]  # type: ignore[possibly-undefined]
    返回 新_f, 新_g, t, ls_func_evals


[文档] LBFGS(优化器): 实现 L-BFGS 算法。 受`minFunc`启发。 ``_. .. 警告:: 此优化器不支持按参数选项和参数组(只能有一个)。 目前所有参数必须位于单个设备上。这将在未来得到改进。 .. 警告:: Right now all parameters have to be on a single device. This will be improved in the future. .. 注意:: 这是一个非常占用内存的优化器(它需要额外的 ``param_bytes * (history_size + 1)`` 字节)。如果它无法适应内存 的话,尝试减小历史大小,或者使用不同的算法。 参数: params(可迭代对象):要优化的参数的可迭代对象。参数必须是实数。 lr (float, 可选): 学习率 (默认: 1) max_iter (int, 可选): 每次优化步骤的最大迭代次数 (默认: 20) max_eval (int, 可选): 每次优化的函数评估的最大次数 步长(默认:max_iter * 1.25)。 tolerance_grad(浮点数,可选):第一阶最优性的终止容差 (默认:1e-7)。 tolerance_change(浮点数,可选):函数的终止容差 值/参数更改(默认:1e-9)。 history_size(整型,可选):更新历史大小(默认:100)。 line_search_fn(字符串,可选):可以是 'strong_wolfe' 或 None(默认:None)。 "文档" 定义 __init__( 自身, 参数: 参数 T, 学习率: 并集[float, 张量] = 1, 最大迭代次数: 整型 = 20, 最大评估: 可选[int] = , 容差梯度: 浮点数 = 1e-7, 容差变化: 浮点数 = 1e-9, 历史大小: 整型 = 100, 线搜索函数: 可选[字符串] = , ): 如果 isinstance(学习率, 张量) 学习率.元素数量() != 1: 提升 ValueError("Tensor lr 必须是 1 个元素") 如果 0.0 <= 学习率: 提升 ValueError(f"无效的学习率:"{学习率}") 如果 最大评估 is : 最大评估 = 最大迭代 * 5 // 4 默认 = 字典( 学习率=学习率, 最大迭代=最大迭代次数, 最大评估次数=最大评估次数, 容差梯度=容差梯度, 容差变化=容差变化, 历史大小=历史大小, 线搜索函数=线索搜索函数, ) 超级().__init__(参数, 默认值) 如果 长度(自身.参数组) != 1: 提升 ValueError( "LBFGS 不支持每个参数的选项(参数组)" ) 自身._params = 自身.参数组[0] [参数] 自身._numel_cache = 定义 numel(自身): 如果 自身.numel_cache is : 自身.numel_cache = 总和( 2 * p.元素数量() 如果 火炬.是复杂的(p) 否则 p.元素数量() p 自身.参数 ) 返回 自身.numel_cache 定义 gather_flat_grad(自身): 视图 = 输入文本为空,请提供需要翻译的文本 p 自身.参数: 如果 p.梯度 is : 视图 = p.(p.元素数量()).零_() 如果...否则 p.研究生.is_sparse: 视图 = p.研究生.转换为稠密格式().视图(-1) else: 视图 = p.研究生.视图(-1) 如果 火炬.是复杂的(视图): 查看 = 火炬.真实查看(视图).视图(-1) 查看次数.append(视图) 返回 火炬.(查看次数, 0) 定义 _添加梯度(自身, 步长, 更新): 偏移 = 0 p 自身._params: 如果 火炬.是复杂的(p): p = 火炬.真实查看(p) numel = p.元素数量() 以查看方式避免过时的逐点语义 p.加_(更新[偏移 : 偏移 + 元素数量].以查看方式(p), 阿尔法=步长) 偏移 += numel 断言 偏移 == 自身.numel() 定义 clone_param(自身): 返回 [p.克隆(内存格式=火炬.连续格式) p 自身.参数] 定义 设置参数(自身, 参数数据): p, 数据 zip(自身.参数, 参数数据): p.复制_(p 数据) 定义 方向评估(自身, 闭包, x, t, d): 自身.添加梯度(t, d) 损失 = float(闭包()) 平滑梯度 = 自身._收集平滑梯度() 自身._设置参数(x) 返回 损失, 平滑梯度
[文档] @torch.不梯度() 定义 步长(自身, 闭包): 执行单个优化步骤。 参数: 闭包(Callable):一个重新评估模型的闭包 和返回损失。 "文档" 断言 长度(自身.参数组) == 1 # 确保闭包始终在启用梯度的情况下被调用 闭包 = 火炬.启用梯度()(闭包) 群组 = 自身.参数组[0] 左右 = 群组["lr"] 最大迭代次数 = 群组["max_iter"] max_eval = 群组["max_eval"] tolerance_grad = 群组["tolerance_grad"] tolerance_change = 群组["容忍度变化"] 线性搜索函数 = 群组["线性搜索函数"] 历史大小 = 群组[历史大小] # LBFGS 只有全局状态,但我们将其注册为状态,因为这样有助于在 load_state_dict 中的类型转换 # 第一个参数,因为这有助于在加载状态字典时的类型转换 状态 = 自身.状态[自身._参数[0]] 状态.setdefault("函数评估", 0) 状态.setdefault("迭代次数", 0) # 评估初始 f(x) 及 df/dx 原始损失 = 闭包() 损失 = float(原始损失) 当前评估 = 1 状态[函数评估] += 1 平坦梯度 = 自身.收集平坦梯度() 优化条件 = 平坦梯度.绝对值().最大值() <= 容差梯度 # 最佳条件 如果 opt_cond: 返回 原始损失 状态中缓存的张量(用于跟踪) d = 状态.获取("d") t = 状态.获取("t") 旧目录 = 状态.获取("旧目录") 旧步骤 = 状态.获取("旧步骤") ro = 状态.获取("ro") H_diag = 状态.获取("H_diag") 前一次平坦梯度 = 状态.获取("前一次平坦梯度") 前一次损失 = 状态.获取("前一次损失") n_iter = 0 优化最大迭代次数为 max_iter n_iter < max_iter: 追踪迭代次数 n_iter += 1 状态["n_iter"] += 1 ############################################################ 计算梯度下降方向 ############################################################ 如果 状态[n_iter] == 1: d = flat_grad.否定() old_dirs = 输入文本为空,请提供需要翻译的文本 旧步骤 = 输入文本为空,请提供需要翻译的文本 ro = 输入文本为空,请提供需要翻译的文本 H 诊断 = 1 else: # 执行 lbfgs 更新(更新内存) y = 平滑梯度.(前平滑梯度) s = d.(t) ys = y.(s) # y*s 如果 ys > 1e-10: 更新内存 如果 长度(旧目录) == 历史大小: 向前移动(有限内存)历史记录 旧目录.流行(0) 旧步骤.流行(0) ro.流行(0) 存储新的方向/步骤 旧目录.append(y) 旧步骤.append(s) .append(1.0 / ys) 更新初始 Hessian 近似矩阵的尺度 H_diag = ys / y.(y) # (y*y) 计算近似的(L-BFGS)逆 Hessian 乘以梯度 num_old = 长度(old_dirs) 如果 "al" 状态: 状态["al"] = [] * 历史大小 al = 状态["al"] L-BFGS 循环中的迭代塌缩为仅使用一个缓冲区 q = 平方梯度.否定() for i 范围(旧数量 - 1, -1, -1): al[i] = old_stps[i].(q) * ro[i] q.加_(old_dirs[i] 阿尔法=-al[i]) 乘以初始 Hessian r/d 是最终方向 d = r = 火炬.(q, H_diag) for i 范围(原数): 是_i = 旧目录[i].(r) * ro[i] r.加_(旧步骤[i] 阿尔法=al[i] - be_i) 如果 前平面梯度 is : 前面平坦梯度 = 平坦梯度.克隆(内存格式=火炬.连续格式) else: 前一个平坦梯度.复制_(平坦梯度) 前一个损失 = 损失 ############################################################ # 计算步长 ############################################################ 重置步长初始猜测 如果 状态[n_iter] == 1: t = 最小(1.0, 1.0 / flat_grad.绝对值().总和()) * 左右 else: t = 左右 方向导数 gtd = 平坦梯度.(d) # g * d 方向导数低于公差 如果 待办事项管理 > -容差变化: 断开 # 可选的行搜索:用户函数 ls_func_evals = 0 如果 行搜索函数 is : 执行行搜索,使用用户函数 如果 line_search_fn != "strong_wolfe": 提升 运行时错误("仅支持 'strong_wolfe'") else: x 初始化 = 自身._克隆参数() 定义 对象函数(x, t, d): 返回 自身._方向评估(闭包, x, t, d) 损失, 平滑梯度, t, ls_func_evals = 强大的沃尔夫( 目标函数, 初始值 x, t, d, 损失, 平滑梯度, gtd ) 自身._添加梯度(t, d) 优化条件 = 平滑梯度.绝对值().最大值() <= 容差梯度 else: # 无线搜索,仅以固定步长移动 自身._添加梯度(t, d) 如果 n_iter != max_iter: # 仅在非最后一次迭代时重新评估函数 # 我们这样做的原因是:在随机环境中, #此处无需重新评估该函数 火炬.启用梯度(): 损失 = float(闭包()) 平坦梯度 = 自身._收集平坦梯度() 优化条件 = 平滑梯度.绝对值().最大值() <= 容差梯度 ls_func_evals = 1 # 更新函数评估 当前评估 += ls_func_evals 状态[函数评估] += ls_func_evals ############################################################ # 检查条件 ############################################################ 如果 迭代次数 == 最大迭代次数: 断开 如果 当前评估次数 >= 最大评估次数: 断开 最优条件 如果 可选条件: 断开 # 缺乏进展 如果 d.(t).绝对值().最大值() <= 容差变化: 断开 如果 绝对值(损失 - 前一个损失) < 容差变化: 断开 状态["d"] = d 状态["t"] = t 状态["旧目录"] = 旧目录 状态["旧步骤"] = 旧步骤 状态["罗"] = 罗奥 状态["H_对角"] = H_对角 状态["前向平坦梯度"] = 前向平坦梯度 状态["前向损失"] = 前向损失 返回 原始损失

© 版权所有 PyTorch 贡献者。

使用 Sphinx 构建,并使用 Read the Docs 提供的主题。

文档

查看 PyTorch 的全面开发者文档

查看文档

教程

深入了解初学者和高级开发者的教程

查看教程

资源

查找开发资源,获取您的疑问解答

查看资源