在线优化算法 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次),显然,如果要拿到最多的利益,你要做的就是尽 ...
随机推荐
- JVM系列八(虚拟机性能监控命令).
jps JVM Process Status Tool,显示指定系统内所有的 HotSpot 虚拟机进程.显示信息包括虚拟机执行主类名称以及这些进程的本地虚拟机唯一ID(Local Virtual M ...
- springboot项目中thymeleaf布局应用
.katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...
- .NET写入文件操作
2018-01-16 22:44:35 许多程序需要记录运行日志,这就需要将程序运行记录写入本机,一般是.txt 文本或.csv 文件.具体操作如下: 一.C# using System.IO; ...
- sklearn-转换器与机器学习流程
一.sklearn估计器 二.机器学习开发流程 .
- 五分钟了解Semaphore
一.前言 多个线程之间的同步,我们会用到Semaphore,翻译成中文就是信号量.使用Semaphore可以限制多个线程对同一资源的访问.我们先看下C#中对Semaphore的定义,如下图: 翻译成中 ...
- Mol Cell Proteomics. | MARMoSET – Extracting Publication-ready Mass Spectrometry Metadata from RAW Files
本文是马克思普朗克心肺研究所的三名研究者Marina Kiweler.Mario Looso和Johannes Graumann发表在8月刊的MCP的一篇文章. 由于Omics实验经常涉及数百个数据文 ...
- Vue-Cli4笔记
Vue-Cli4与Vue-Cli2区别浅谈 当时学习 Vue-Cli 的时候看的是 Vue-Cli2 的相关教程,当把 package.json 上传 github 的时候提醒有安全问题,于是准备使用 ...
- python通过scapy编写arp扫描器
多网卡的情况下发送二层包需要配置网卡 三层包不需要配置接口发包方法: sr() 发送三层数据包,等待接收一个或者多个数据包的响应 sr1() 发送三层数据包,只会接收一个数据包的响应 srp() 发送 ...
- 题解 SP2916 【GSS5 - Can you answer these queries V】
前言 最近沉迷于数据结构,感觉数据结构很有意思. 正文 分析 先来分类讨论一下 1. \(x2<y1\) 如果 \(y1<x2\) 的话,答案 \(=\max \limits_{ y1 \ ...
- LeetCode45——从搜索算法推导到贪心
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode系列的第25篇文章,今天我们一起来看的是LeetCode的第45题,Jump Game II. 有同学后台留言问我说, ...