# 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_对角
状态[
"前向平坦梯度"] =
前向平坦梯度
状态[
"前向损失"] =
前向损失
返回
原始损失