在线优化算法 FTRL 的原理与实现
在线学习想要解决的问题
在线学习 ( \(\it{Online \;Learning}\) ) 代表了一系列机器学习算法,特点是每来一个样本就能训练,能够根据线上反馈数据,实时快速地进行模型调整,使得模型及时反映线上的变化,提高线上预测的准确率。相比之下,传统的批处理方式需要一次性收集所有数据,新数据到来时重新训练的代价也很大,因而更新周期较长,可扩展性不高。
一般对于在线学习来说,我们致力于解决两个问题: 降低 regret 和提高 sparsity。其中 regret 的定义为:
\]
其中 \(t\) 表示总共 \(T\) 轮中的第 \(t\) 轮迭代,\(\ell_t\) 表示损失函数,\(\bold{w}\) 表示要学习的参数。第二项 \(\min_\bold{w}\sum_{t=1}^T\ell_t(\bold{w})\) 表示得到了所有样本后损失函数的最优解,因为在线学习一次只能根据少数几个样本更新参数,随机性较大,所以需要一种稳健的优化方式,而 regret 字面意思是 “后悔度”,意即更新完不后悔。
在理论上可以证明,如果一个在线学习算法可以保证其 regret 是 \(t\) 的次线性函数,则:
\]
那么随着训练样本的增多,在线学习出来的模型无限接近于最优模型。而毫不意外的,FTRL 正是满足这一特性。
另一方面,现实中对于 sparsity,也就是模型的稀疏性也很看中。上亿的特征并不鲜见,模型越复杂,需要的存储、时间资源也随之升高,而稀疏的模型会大大减少预测时的内存和复杂度。另外稀疏的模型相对可解释性也较好,这也正是通常所说的 L1 正则化的优点。
后文主要考察 FTRL 是如何实现降低 regret 和提高 sparsity 这两个目标的。
FTRL 原理
网上很多资料都是从 FTRL 的几个前辈,FOBOS、RDA 等一步步讲起,本篇就不绕那么大的圈子了,直接从最基本的 OGD 开路到 FTRL 。OGD ( \(\it{online \;gradient \; descent}\) ) 是传统梯度下降的 online 版本,参数更新公式为:
\]
\(t\) 表示第 \(t\) 轮迭代,注意上式中的学习率 \(\eta_t\) 每轮都会变,一般设为 \(\eta_t = \frac{1}{\sqrt{t}}\)
OGD 在准确率上表现不错,即 regret 低,但在上文的另一个考量因素 sparsity 上则表现不佳,即使加上了 L1 正则也很难使大量的参数变零。一个原因是浮点运算很难让最后的参数出现绝对零值;另一个原因是不同于批处理模式,online 场景下每次 \(\bold {w}\) 的更新并不是沿着全局梯度进行下降,而是沿着某个样本的产生的梯度方向进行下降,整个寻优过程变得像是一个“随机” 查找的过程,这样 online 最优化求解即使采用 L1 正则化的方式, 也很难产生稀疏解。正因为 OGD 存在这样的问题,FTRL 才致力于在准确率不降低的前提下提高稀疏性。
相信大部分人对于 \((1.1)\) 式都不陌生,然而其实际等价于下式:
\]
对 \((1.2)\) 式直接求导即可,\(\bold{g}_t + \frac{1}{\eta_t}(\bold{w} - \bold{w}_t) = 0 \;\;\implies\;\; \bold{w} = \bold{w}_t - \eta_t \bold{g}_t\) 。有了 \((1.2)\) 式的基础后,后面 FTRL 的那些奇奇怪怪的变换就能明了了,目的无非也是降低 regret 和提高 sparsity 。
首先,为了降低 regret,FTRL 用 \(\bold{g}_{1:t}\) 代替 \(\bold{g}_t\) ,\(\bold{g}_{1:t}\) 为前 1 到 t 轮损失函数的累计梯度,即 \(\bold{g}_{1:t} = \sum_{s=1}^t \bold{g}_s = \sum_{s=1}^t \nabla \ell_s(\bold{w}_s)\) 。由于在线学习随机性大的特点,累计梯度可避免由于某些维度样本局部抖动太大导致错误判断。这是从 FTL ( \(\it{Follow \; the\; Leader}\) ) 那借鉴而来的,而 FTRL 的全称为 \(\it{Follow \; the\; Regularized \;Leader}\) ,从名字上看其实就是在 FTL 的基础上加上了正则化项,即 \((1.2)\) 式中的
\(||\bold{w} - \bold{w}_t||_2^2\) 项。这意味着每次更新时我们不希望新的 \(\bold{w}\) 离之前的 \(\bold{w}_t\) 太远 (这也是有时其被称为 FTRL-proximal 的原因),这同样是为了降低 regret,在线学习噪音大,若一次更新错得太远后面难以收回来,没法轻易“后悔”。
其次,为提高 sparsity ,最直接的方法就是无脑加 L1 正则。但这里的问题是上文中 OGD 加了 L1 正则不能产生很好的稀疏性,那么 FTRL 为什么就能呢?这在后文的具体推导中会逐一显现,耐心看下去就是。另外 FTRL 2013 年的工程论文中也加上了 L2 正则,所以综合上述几点,FTRL 的更新公式变为:
\]
其中 \(\sigma_s = \frac{1}{\eta_s} - \frac{1}{\eta_{s-1}}\) ,则 \(\sigma_{1:t} = \sum_{s=1}^t \sigma_s = \frac{1}{\eta_s}\) ,主要是为了后面推导和实现方便而这么设置,后文再述。
下面可以推导 FTRL 的算法流程,将 \((1.3)\) 式中的 \(||\bold{w} - \bold{w}_s||_2^2\) 展开:
\]
由于 \(\frac12 \sum\limits_{s=1}^t \sigma_s||\bold{w}_s||_2^2\) 相对于要优化的 \(\bold{w}\) 是一个常数可以消去,并令 \(\bold{z}_t = \bold{g}_{1:t} - \sum\limits_{s=1}^t \sigma_s\bold{w}_s\) ,于是 \((1.4)\) 式变为:
\]
将特征的各个维度拆开成独立的标量最小化问题,\(i\) 为 第 \(i\) 个特征:
\]
\((1.6)\) 式是一个无约束的非平滑参数优化问题,其中第二项 \(\lambda_1|w_i|\) 在 \(w_i = 0\) 处不可导,因而常用的方法是使用次导数 (详见附录1),这里直接上结论: 定义 \(\phi \in \partial |w_i^*|\) 为 \(|w_i|\) 在 \(w_i^*\) 处的次导数,于是有:
\begin{cases}
\quad\quad\{1\} &\quad \text{if}\;\; w_i^* > 0 \\[1ex]
-1 < \phi < 1 & \quad \text{if}\;\; w_i^* = 0 \\[1ex] \tag{1.7}
\quad\;\;\{-1\} &\quad \text{if}\;\; w_i^* < 0
\end{cases}
\]
有了 \(|w_i|\) 的次导数定义后,对 \((1.6)\) 式求导并令其为零:
\]
上式中 \(\lambda_1 > 0\, , \;\; \left(\lambda_2 + \sum_{s=1}^t \sigma_s \right) > 0\) ,下面对 \(z_{t,i}\) 的取值分类讨论:
(1) \(|z_{t, i}| < \lambda_1\) ,那么 \(w_i = 0\) 。因为若 \(w_i > 0\) ,根据 \((1.7)\) 式 \(\phi = 1\) ,则 \((1.8)\) 式左侧 \(> 0\) ,该式不成立;同样若 \(w_1 < 0\),则 \((1.8)\) 式左侧 \(< 0\),不成立。
(2) \(z_{t, i} > \lambda_1\),则 \(\phi = -1 \implies w_i = -\frac{1}{\lambda_2 + \sum_{s=1}^t \sigma_s} (z_{t, i} - \lambda_1) < 0\) 。因为若 \(w_i > 0\),\(\phi = 1\),\((1.8)\) 式左侧 \(> 0\),不成立;若 \(w_i = 0\),由 \((1.8)\) 式 \(\phi = -\frac{z_{t,i}}{\lambda_1} < -1\) ,与 \((1.7)\) 式矛盾。
(3) \(z_{t,i} < -\lambda_1\),则 \(\phi = 1 \implies w_i = -\frac{1}{\lambda_2 + \sum_{s=1}^t \sigma_s} (z_{t, i} + \lambda_1) > 0\) 。因为若 \(w_i < 0\),\(\phi = -1\),\((1.8)\) 式左侧 $ < 0$,不成立;若 \(w_i = 0\),由 \((1.8)\) 式 \(\phi = -\frac{z_{t,i}}{\lambda_1} > 1\) ,与 \((1.7)\) 式矛盾。
综合这几类情况,由 $(1.8) $ 式得到 \(w_{t,i}\) 的更新公式:
\begin{cases}
\qquad\qquad \large{0} & \text{if}\;\; |z_{t,i}| < \lambda_1 \\[2ex]
-\frac{1}{\lambda_2 + \sum_{s=1}^t\sigma_s} \left(z_{t,i} - \text{sgn}(z_{t,i})\cdot\lambda_1 \right) & \text{otherwise} \tag{1.9}
\end{cases}
\]
可以看到当 \(z_{t,i} = \left(g_{1:t, i} - \sum_{s=1}^t \sigma_s w_{s, i} \right) < \lambda_1\) 时,参数置为零,这就是 FTRL 稀疏性的由来。另外加入 L2 正则并没有影响模型的稀疏性,从 \((1.9)\) 式看只是使得分母变大,进而 \(w_i\) 更趋于零了,这在直觉上是符合正则化本身的定义的。
观察 \((1.9)\) 式还遗留一个问题,$\sigma $ 的值是什么呢?这牵涉到 FTRL 的学习率设置。当然严格意义上的学习率是 \(\eta_t\) ,而 \(\sigma_t = \frac{1}{\eta_t} - \frac{1}{\eta_{t-1}}\) ,论文中这样定义可能是为了推导和实现的方便。前文 \((1.1)\) 式中 OGD 使用的是一个全局学习率 \(\eta_t = \frac{1}{\sqrt{t}}\) ,会随着迭代轮数的增加而递减,但该方法的问题是所有特征维度都使用了一样的学习率。
FTRL 采用的是 Per-Coordinate Learning Rate,即每个特征采用不同的学习率,这种方法考虑了训练样本本身在不同特征上分布的不均匀性。如果一个特征变化快,则对应的学习率也会下降得快,反之亦然。其实近年来随着深度学习的流行这种操作已经是很常见了,常用的 AdaGrad、Adam 等梯度下降的变种都蕴含着这类思想。FTRL 中第 \(t\) 轮第 \(i\) 个特征的学习率为:
\]
这样 \((1.9)\) 式中的 \(\sum_{s=1}^t \sigma_s\) 为:
\sum\limits_{s=1}^t \sigma_s &= (\frac{1}{\eta_t} - \frac{1}{\eta_{t-1}}) + (\frac{1}{\eta_{t-1}} - \frac{1}{\eta_{t-2}}) + \cdots + (\frac{1}{\eta_1} - \frac{1}{\eta_0}) \\
&=\;\; \frac{1}{\eta_t} \;\;=\;\; \frac{\beta + \sqrt{\sum_{s=1}^tg_{s,i}^2}}{\alpha} \tag{1.11}
\end{align*}
\]
其中 \(\alpha, \beta\) 为超参数,论文中建议 \(\beta\) 设为 1,而 \(\alpha\) 则根据情况选择。\(g_{s,i}\) 为第 \(s\) 轮第 \(i\) 个特征的偏导数,于是 \((1.9)\) 式变为:
\begin{cases}
\qquad\qquad \large{0} & \quad\text{if}\;\; |z_{t,i}| < \lambda_1 \\[2ex]
- \left(\lambda_2 + \frac{\beta + \sqrt{\sum_{s=1}^t g_{s,i}^2}}{\alpha} \right)^{-1} \left(z_{t,i} - \text{sgn}(z_{t,i})\cdot\lambda_1 \right) & \quad \text{otherwise} \tag{1.12}
\end{cases}
\]
综合 \((1.10)\) 式和 \((1.12)\) 式可以看出,学习率 \(\eta_{t,i}\) 越大,则参数 \(w\) 更新幅度越大,这与学习率的直觉定义相符。
FTRL 实现
完整代码见 ( https://github.com/massquantity/Ftrl-LR ) ,实现了多线程版本 FTRL 训练 Logistic Regression 。
对于算法的实现来说,首先需要得到完整的算法流程。仔细审视 \((1.12)\) 式,要在 \(t + 1\) 轮更新 \(w_{t+1, i}\) 需要哪些值? 需要 \(\{ \;z_{t,i}, \,g_{t,i}, \,\alpha, \,\beta, \,\lambda_1, \,\lambda_2 \; \}\) ,后四个为预先指定的超参数,对于 \(z_{t,i}\) ,注意其定义有可以累加的特性 :
z_{t,i} &= g_{1:t, i} - \sum\limits_{s=1}^t \sigma_{s, i} w_{s,i} \\
&= \sum\limits_{s=1}^t g_{s,i} - \sum\limits_{s=1}^t \sigma_{s, i} w_{s,i} \\
&= z_{t-1,i} + g_{t,i} - \sigma_{t,i}w_{t,i} \\[1ex]
&= z_{t-1,i} + g_{t,i} - \left(\frac{1}{\eta_{t,i}} - \frac{1}{\eta_{t-1, i}} \right) w_{t,i} \\[1ex]
&= z_{t-1,i} + g_{t,i} - \frac{\sqrt{\sum_{s=1}^t g_{s,i}^2} - \sqrt{\sum_{s=1}^{t-1} g_{s,i}^2}}{\alpha} w_{t,i}
\end{aligned}
\]
所以我们只需存储上一轮迭代得到的三个量 : \(z_{t-1,i}, \, w_{t,i}, \sqrt{\sum_{s=1}^t g_{s,i}^2}\) ,并在本轮迭代中计算 \(g_{t,i}\) ,就能不断更新参数了。\(g_{t,i}\) 为损失函数对第 \(i\) 个特征的偏导数,\(\text{Logistic Regression}\) 的损失函数是 \(\text{Log Loss}\),这里直接给出结论,具体推导见附录 2:
\]
其中 \(S(\cdot)\) 为 Sigmoid函数,\(x_i\) 为第 \(i\) 个特征值, \(y \in \{-1, + 1\}\) 为标签,\(f(\bold{x}_t) = \sum_{i=1}^I w_ix_i\) 。下面就可以给出完整的算法流程了,其中为方便表示定义了 \(n_i = \sqrt{\sum_{s=1}^t g_{s,i}^2}\) :
输入: 参数 \(\alpha, \,\beta, \,\lambda_1, \,\lambda_2\)
初始化: \(\bold{z}_0 = \bold{0}, \; \bold{n}_0 = \bold{0}\)
$\text{for t = 1 to T} : $
\(\qquad\) 收到一个样本 \(\{\bold{x}_t, y_t\}\) ,\(y_t \in \{-1, + 1\}\) 。令 \(I\) 为所有不为零的特征集合,即 \(I = \{i \,|\, x_i \neq 0\}\)
\(\qquad\) \(\text{for} \;\;i \in I\) 更新 \(w_{t,i}\) :
\[w_{t,i} =
\begin{cases}
\qquad\qquad \large{0} & \quad\text{if}\;\; |z_{i}| < \lambda_1 \\[2ex]
- \left(\lambda_2 + \frac{\beta + \sqrt{n_i}}{\alpha} \right)^{-1} \left(z_{i} - \text{sgn}(z_{i})\cdot\lambda_1 \right) & \quad \text{otherwise}
\end{cases}
\] \(\qquad\) 使用更新后的 \(\bold{w}_t\) 计算 \(f(\bold{x}_t) = \bold{w}_t \cdot \bold{x}_t\)
\(\qquad\) \(\text{for all} \; i \in I :\)
\(\qquad\qquad\) \(g_i = y_t (S(y_t f(\bold{x}_t)) - 1) x_i\)
\(\qquad\qquad\) \(\sigma_i = \frac{\sqrt{n_i + g_i^2} - \sqrt{n_i}}{\alpha}\)
\(\qquad\qquad\) \(z_i \leftarrow z_i + g_i - \sigma_i w_{t,i}\)
\(\qquad\qquad\) \(n_i \leftarrow n_i + g_i^2\)
\(\qquad\) \(\text{end for}\)
\(\text{end for}\)
如上文所述,代码中使用了一个 ftrl_model_unit
类来存储三个量 \(z_{i}, \, w_{i}, n_i\) :
class ftrl_model_unit
{
public:
double wi;
double w_ni;
double w_zi;
ftrl_model_unit()
{
wi = 0.0;
w_ni = 0.0;
w_zi = 0.0;
}
ftrl_model_unit(double mean, double stddev)
{
wi = utils::gaussian(mean, stddev);
w_ni = 0.0;
w_zi = 0.0;
}
}
更新参数的核心步骤为:
void ftrl_trainer::train(int y, const vector<pair<string, double> > &x)
{
ftrl_model_unit *thetaBias = pModel->getOrInitModelUnitBias();
vector<ftrl_model_unit *> theta(x.size(), nullptr);
int xLen = x.size();
for (int i = 0; i < xLen; ++i) {
const string &index = x[i].first;
theta[i] = pModel->getOrInitModelUnit(index); // 获取相应的 ftrl_model_unit
}
for (int i = 0; i <= xLen; ++i) {
ftrl_model_unit &mu = i < xLen ? *(theta[i]) : *thetaBias;
if (fabs(mu.w_zi) <= w_l1)
mu.wi = 0.0;
else {
mu.wi = (-1) *
(1 / (w_l2 + (w_beta + sqrt(mu.w_ni)) / w_alpha)) *
(mu.w_zi - utils::sgn(mu.w_zi) * w_l1); // 更新 wi
}
}
double bias = thetaBias->wi;
double p = pModel->forecast(x, bias, theta); // 计算 f(x)
double mult = y * (1 / (1 + exp(-p * y)) - 1);
for (int i = 0; i <= xLen; ++i) {
ftrl_model_unit &mu = i < xLen ? *(theta[i]) : *thetaBias;
double xi = i < xLen ? x[i].second : 1.0;
double w_gi = mult * xi; // 更新 gi
double w_si = 1 / w_alpha * (sqrt(mu.w_ni + w_gi * w_gi) - sqrt(mu.w_ni));
mu.w_zi += w_gi - w_si * mu.wi; // 更新 zi
mu.w_ni += w_gi * w_gi; // 更新 ni
}
}
Appendix 1: 次导数
\((1.7)\) 式中使用了 \(f(x) = |x|\) 的次导数,这里做一下具体推导。首先次导数的定义 —— 凸函数 \(f: \text{I} \rightarrow \mathbb{R}\) 在开区间 \(\text{I}\) 内的点 \(x_0\) 的次导数 \(c\) 满足:
\]
其物理含义是通过 \(x_0\) 下方的直线的斜率,如下图,\(f(x)\) 在 \(x_0\) 处不可导,则经过 \(x_0\) 画一条红线,总是位于 \(f(x)\) 下方,其斜率就是次导数 \(c\) 。可以看出很多时候次导数并不唯一。
对于 \(f(x) = |x|\) 来说,\(x < 0\) 时,次导数为单元素集合 \(\{-1\}\) ,\(x > 0\) 时为 \(\{1\}\) 。而在 \(x = 0\) 处则不连续,根据次导数的定义,\(f(x) - f(0) \geqslant c\,(x - 0), \; f(x) \geqslant c\,x\) ,则满足该式的 \(c \in [-1, 1]\) ,因而 \(f(x) = |x|\) 的次导数为 (如下图所示):
\begin{cases}
&\;\;\{1\} &\text{if}\;\; x > 0 \\[1ex]
&[-1,1] & \text{if}\;\; x = 0 \\[1ex]
&\;\{-1\} &\text{if}\;\; x < 0
\end{cases}
\]
Appendix 2: $\text{Log Loss} $
FTRL 的算法流程中每一轮更新都要计算损失函数对每个特征分量的偏导数, 论文 中写的是使用梯度,但实际上是梯度的一个分量,即偏导数,这里就不作区分了。\(\text{Log Loss}\) 即 \(\text{Logistic Loss}\) ,其具体由来可参阅前文 《常见回归和分类损失函数比较》。仅考虑一个特征的参数 \(w_i\) ,\(y \in \{-1, + 1\}\) 为标签,$\text{Log Loss} $ 的形式为:
\]
其中 \(f(\bold{x}_t) = \sum_{i=1}^I w_ix_i\) ,对 \(w_i\) 的偏导数为:
\frac{\partial \mathcal{L}}{\partial w_i} &= \frac{e^{-y\,f(\bold{x})}}{1 + e^{-y\,f(\bold{x})}} (-yx_i) \\[1ex]
&= y\left(\frac{1}{1 + e^{-yf(\bold{x})}} - 1 \right)x_i \\[1ex]
&= y(S(yf(\bold{x})) - 1)x_i
\end{align*}
\]
FTRL 论文 中采用的标签形式是 \(y \in \{0,1\}\) ,因而损失函数的形式稍有不同:
\]
其中 \(p(\bold{w}^T\bold{x}) = \frac{1}{1 + e^{- \bold{w}^T \bold{x}}}\) 为 Sigmoid 函数,其导数为:
\]
因而利用这个性质我们可以求出 \((3.2)\) 式关于 \(w_i\) 的偏导数:
\frac{\partial{\mathcal{L}}}{\partial w_i}
&= \left(-y \frac{1}{p(\bold{w}^T\bold{x})} + (1-y)\frac{1}{1 - p(\bold{w}^T\bold{x})} \right) \frac{\partial p(\bold{w}^T \bold{x})}{\partial w_i} \\[1ex]
&= \left(-y \frac{1}{p(\bold{w}^T\bold{x})} + (1-y)\frac{1}{1 - p(\bold{w}^T\bold{x})} \right)p(\bold{w}^T\bold{x}) (1 - p(\bold{w}^T\bold{x})) x_i \qquad\text{利用(3.3)式} \\[1ex]
&= \left(-y(1 - p(\bold{w}^T\bold{x}) + (1-y)p(\bold{w}^T\bold{x})\right) x_i \\[1ex]
&= (p(\bold{w}^T\bold{x}) - y) x_i
\end{align*}
\]
/
在线优化算法 FTRL 的原理与实现的更多相关文章
- Alink漫谈(十二) :在线学习算法FTRL 之 整体设计
Alink漫谈(十二) :在线学习算法FTRL 之 整体设计 目录 Alink漫谈(十二) :在线学习算法FTRL 之 整体设计 0x00 摘要 0x01概念 1.1 逻辑回归 1.1.1 推导过程 ...
- Alink漫谈(十三) :在线学习算法FTRL 之 具体实现
Alink漫谈(十三) :在线学习算法FTRL 之 具体实现 目录 Alink漫谈(十三) :在线学习算法FTRL 之 具体实现 0x00 摘要 0x01 回顾 0x02 在线训练 2.1 预置模型 ...
- 各大公司广泛使用的在线学习算法FTRL详解
各大公司广泛使用的在线学习算法FTRL详解 现在做在线学习和CTR常常会用到逻辑回归( Logistic Regression),而传统的批量(batch)算法无法有效地处理超大规模的数据集和在线数据 ...
- 各大公司广泛使用的在线学习算法FTRL详解 - EE_NovRain
转载请注明本文链接:http://www.cnblogs.com/EE-NovRain/p/3810737.html 现在做在线学习和CTR常常会用到逻辑回归( Logistic Regression ...
- 广告点击率预测(CTR) —— 在线学习算法FTRL的应用
FTRL由google工程师提出,在13的paper中给出了伪代码和实现细节,paper地址:http://www.eecs.tufts.edu/~dsculley/papers/ad-click-p ...
- 模拟退火算法SA原理及python、java、php、c++语言代码实现TSP旅行商问题,智能优化算法,随机寻优算法,全局最短路径
模拟退火算法SA原理及python.java.php.c++语言代码实现TSP旅行商问题,智能优化算法,随机寻优算法,全局最短路径 模拟退火算法(Simulated Annealing,SA)最早的思 ...
- PyTorch-Adam优化算法原理,公式,应用
概念:Adam 是一种可以替代传统随机梯度下降过程的一阶优化算法,它能基于训练数据迭代地更新神经网络权重.Adam 最开始是由 OpenAI 的 Diederik Kingma 和多伦多大学的 Jim ...
- Adam优化算法
Question? Adam 算法是什么,它为优化深度学习模型带来了哪些优势? Adam 算法的原理机制是怎么样的,它与相关的 AdaGrad 和 RMSProp 方法有什么区别. Adam 算法应该 ...
- Bandit:一种简单而强大的在线学习算法
假设我有5枚硬币,都是正反面不均匀的.我们玩一个游戏,每次你可以选择其中一枚硬币掷出,如果掷出正面,你将得到一百块奖励.掷硬币的次数有限(比如10000次),显然,如果要拿到最多的利益,你要做的就是尽 ...
随机推荐
- eslint常用三种校验语句
1.关闭对整段代码的校验 /* eslint-disable */ code /* eslint-enable */ 2.关闭当前行代码的校验 line code // eslint-disable- ...
- 如何理解SiamRPN++?
如何理解SiamRPN++? 目标跟踪: 使用视频序列第一帧的图像(包括bounding box的位置),来找出目标出现在后序帧位置的一种方法. 孪生网络结构: 在进入到正式理解SiamRPN++之前 ...
- 【分布式锁】02-使用Redisson实现公平锁原理
前言 前面分析了Redisson可重入锁的原理,主要是通过lua脚本加锁及设置过期时间来保证锁执行的原子性,然后每个线程获取锁会将获取锁的次数+1,释放锁会将当前锁次数-1,如果为0则表示释放锁成功. ...
- Jenkins的凭证管理
Jenkins的凭证管理 什么是凭证? 凭证(cridential)是Jenkins进行受限操作时的凭据.比如使用SSH登录远程机器时,用户名和密码或SSH key就是凭证.而这些凭证不可能以明文写在 ...
- Fink SQL 实践之OVER窗口
问题场景 Flink SQL 是一种使用 SQL 语义设计的开发语言,用它解决具体业务需求是一种全新体验,类似于从过程式编程到函数式编程的转变一样,需要一个不断学习和实践的过程.在看完了 Flink ...
- java-根据用户输入的成绩来判断等级(新手)
//创建的一个包名. package qige; //导入的一个包.import java.util.Scanner; //定义一个类.public class Zy2 { //公共静态的主方法. p ...
- 源码解读 Golang 的 sync.Map 实现原理
简介 Go 的内建 map 是不支持并发写操作的,原因是 map 写操作不是并发安全的,当你尝试多个 Goroutine 操作同一个 map,会产生报错:fatal error: concurrent ...
- Journal of Proteome Research | Quantifying Protein-Specific N-Glycome Profiles by Focused Protein and Immunoprecipitation Glycomics (分享人:潘火珍)
文献名:Quantifying Protein-Specific N-Glycome Profiles by Focused Protein and Immunoprecipitation Glyco ...
- 强智教务系统验证码识别 Tensorflow CNN
强智教务系统验证码识别 Tensorflow CNN 一直都是使用API取得数据,但是API提供的数据较少,且为了防止API关闭,先把验证码问题解决 使用Tensorflow训练模型,强智教务系统的验 ...
- 关于手机淘宝3.25bug我的一些思考与建议
这两天被手淘ios版3.25bug刷屏了,影响还是挺大的,仅3.25日当天截止到下午5点在微博上的话题阅读量,已经突破8000万.给广大网友带来一次吃瓜盛宴.我们先简单回顾下这个bug的故事线: 我查 ...