0%

这两天计划将之前部署在MacBook上面的博客迁移到实验室的Linux电脑上,并且在上面维护,同时由于hexo推送到github上面的文件并不是原始文件,所以希望做一个文件备份,防止丢失。在这里记录一下相关的操作,避免之后再次迁移的时候需要做重复性的工作。

Hexo博客备份

原本博客对应的repo应当是username.github.io,其中master分支用来管理对应的博客,新建一个backup分支用来存放博客的原始文件。

1
2
3
git add -A
git commit -m "source file backup"
git push -u origin main:backup --force

新机器环境配置

首先安装nvm,然后利用nvm安装node

1
nvm install --lts

之后可以检测是否已经安装成功

1
2
node -v
npm -v

确认node环境没有问题之后,我们可以进行hexo的安装

1
2
npm install -g hexo-cli
npm install -g hexo

环境安装完成之后就可以尝试在本地重新部署博客,拉取github上面的备份

1
git clone https://github.com/path/to/your/repo

转换到对应的分支

1
git checkout origin/backup

进行对应的环境配置

1
npm install

在本地测试是否博客可以正常部署

1
hexo g && hexo s

更新相关Package

我的博客在部署之后就没有进行过相关包的更新,所以很多包都和最新版本相差较多,可以输入以下命令来查看过时的包。

1
npm outdated

可以看到有许多的包和最新版本已经差别较大,我们这里会尝试进行更新,但是直接更新可能会有依赖相关的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
Package                         Current  Wanted  Latest  Location                                     Depended by
hexo 4.2.1 4.2.1 6.3.0 node_modules/hexo hexo
hexo-deployer-git 2.1.0 2.1.0 4.0.0 node_modules/hexo-deployer-git hexo
hexo-deployer-rsync 1.0.0 1.0.0 2.0.0 node_modules/hexo-deployer-rsync hexo
hexo-generator-archive 1.0.0 1.0.0 2.0.0 node_modules/hexo-generator-archive hexo
hexo-generator-category 1.0.0 1.0.0 2.0.0 node_modules/hexo-generator-category hexo
hexo-generator-feed 2.2.0 2.2.0 3.0.0 node_modules/hexo-generator-feed hexo
hexo-generator-index 1.0.0 1.0.0 3.0.0 node_modules/hexo-generator-index hexo
hexo-generator-sitemap 2.1.0 2.2.0 3.0.1 node_modules/hexo-generator-sitemap hexo
hexo-generator-tag 1.0.0 1.0.0 2.0.0 node_modules/hexo-generator-tag hexo
hexo-renderer-ejs 1.0.0 1.0.0 2.0.0 node_modules/hexo-renderer-ejs hexo
hexo-renderer-markdown-it-plus 1.0.4 1.0.6 1.0.6 node_modules/hexo-renderer-markdown-it-plus hexo
hexo-server 1.0.0 1.0.0 3.0.0 node_modules/hexo-server hexo

这里首先安装 npm-check-updates,然后用这个工具来确认相关的依赖是否有问题

1
2
npm install -g npm-check-updates
ncu
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Checking /home/yunchong/Documents/hexo/package.json
[====================] 17/17 100%

hexo ^4.0.0 → ^6.3.0
hexo-deployer-git ^2.1.0 → ^4.0.0
hexo-deployer-rsync ^1.0.0 → ^2.0.0
hexo-generator-archive ^1.0.0 → ^2.0.0
hexo-generator-category ^1.0.0 → ^2.0.0
hexo-generator-feed ^2.2.0 → ^3.0.0
hexo-generator-index ^1.0.0 → ^3.0.0
hexo-generator-sitemap ^2.0.0 → ^3.0.1
hexo-generator-tag ^1.0.0 → ^2.0.0
hexo-renderer-ejs ^1.0.0 → ^2.0.0
hexo-renderer-markdown-it-plus ^1.0.4 → ^1.0.6
hexo-server ^1.0.0 → ^3.0.0

利用ncu来更新对应的package.json文件

1
ncu -u
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Upgrading /home/yunchong/Documents/hexo/package.json
[====================] 17/17 100%

hexo ^4.0.0 → ^6.3.0
hexo-deployer-git ^2.1.0 → ^4.0.0
hexo-deployer-rsync ^1.0.0 → ^2.0.0
hexo-generator-archive ^1.0.0 → ^2.0.0
hexo-generator-category ^1.0.0 → ^2.0.0
hexo-generator-feed ^2.2.0 → ^3.0.0
hexo-generator-index ^1.0.0 → ^3.0.0
hexo-generator-sitemap ^2.0.0 → ^3.0.1
hexo-generator-tag ^1.0.0 → ^2.0.0
hexo-renderer-ejs ^1.0.0 → ^2.0.0
hexo-renderer-markdown-it-plus ^1.0.4 → ^1.0.6
hexo-server ^1.0.0 → ^3.0.0

之后直接用npm就可以对照更新之后的package.json文件进行新版本的安装

1
npm install

指数加权移动平均(EWMA或EWA)是量化交易中一种简单而强大的工具,特别适用于日内交易。它允许交易者快速轻松地跟踪指定时间段内证券的平均价格,并可用于识别趋势并进行交易决策。

通常来说EWMA的数学公式可以表示为如下:

EWMAt=αxt+(1α)EWMAt1\text{EWMA}_{t} = \alpha x_t +(1 - \alpha) \text{EWMA}_{t-1}

所以其关键在于α\alpha的计算,在pandas所提供的api中,提供了alphahalflifespancom这四个表示不同但是相互等价的参数,通常使用的多为alphaspan

其中com即为质心(Center of Mass),他的计算,可以认为是针对于每个时间点的权重的加权平均,所找到的位置,即:

CoM=t=0(1α)tαt=α(1α)t=0t(1α)t1=α(1α)t=0[(1α)t]=α(1α)[t=0(1α)t]=α(1α)[1α]=1αα\begin{aligned} \text{CoM} &= \sum_{t=0}^{\infty} (1 - \alpha)^t \alpha t \\ &= \alpha(1 - \alpha)\sum_{t=0}^{\infty} t(1-\alpha)^{t-1} \\ &= \alpha(1 - \alpha) \sum_{t=0}^{\infty} \left[-(1 - \alpha)^t\right]'\\ &= \alpha(1 - \alpha) \left[-\sum_{t=0}^{\infty} (1 - \alpha)^t\right]'\\ &= \alpha(1 - \alpha) \left[ - \frac{1}{\alpha}\right]' \\ &= \frac{1 - \alpha}{\alpha} \end{aligned}

化简上式,我们可以得到:

α=1/(1+CoM)\alpha = 1 / (1 + \text{CoM})

半衰期(Half-life)即为权重衰减到一半所需要的时间,所以我们可以得到:

(1α)H=0.5α=1exp(log2H)(1 - \alpha)^H = 0.5 \Rightarrow \alpha = 1 - \exp \left(-\frac{\log2}{H}\right)

以上均为时间间隔等长的情况,当面对不同间隔的时间序列的时候,我们可以使用index参数来指定时间序列的时间间隔,这样可以使得计算的结果更加准确。假设两个时间戳的间隔为dt,那么我们可以使用如下的公式来计算alpha

α=1exp(αdt)1(1αdt)=αdt\alpha' = 1 - \exp(-\alpha \text{d}t) \approx 1 - (1 - \alpha \text{d}t) = \alpha \text{d}t

当时间间隔总是为1的时候,实际上和最开始的公式基本等价。

考虑一个情形,依次有三个时间戳,分别为t0t1t2,那么dt1dt2分别为t1 - t0t2 - t1,那么我们可以使用如下的公式来计算alpha

EWMA2=α2x2+(1α2)EWMA1=α2x2+(1α2)(α1x1+(1α1)EWMA0)=α2x2+α1(1α2)x1+(1α1)(1α2)EWMA0\begin{aligned} \text{EWMA}_2 &= \alpha_2 x_2 + (1 - \alpha_2) \text{EWMA}_1 \\ &= \alpha_2 x_2 + (1 - \alpha_2) \left(\alpha_1 x_1 + (1 - \alpha_1) \text{EWMA}_0\right) \\ &= \alpha_2 x_2 + \alpha_1(1 - \alpha_2) x_1 + (1 - \alpha_1)(1 - \alpha_2) \text{EWMA}_0 \end{aligned}

其中t0时刻的权重为:

(1α1)(1α2)=exp(αdt1αdt2)=exp(α(t2t0))(1 - \alpha_1)(1 - \alpha_2) = \exp(-\alpha \text{d}t_1 - \alpha \text{d}t_2) = \exp(-\alpha (t_2 - t_0))

这样即使当中有多个时间戳到达,对于同样间隔的数据点,其权重仍然一致。对应的python代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from typing import Optional

import numpy as np


class EWMA(object):
def __init__(
self,
com: Optional[float] = None,
span: Optional[float] = None,
halflife: Optional[float] = None,
alpha: Optional[float] = None,
) -> None:
assert (
(com is None) + (span is None) + (halflife is None) + (alpha is None)
) == 3, "only one of com, span, halflife, alpha should be not None"
if com is not None:
self.alpha = 1 / (1 + com)
elif span is not None:
self.alpha = 2 / (span + 1)
elif halflife is not None:
self.alpha = 1 - np.exp(np.log(0.5) / halflife)
elif alpha is not None:
self.alpha = alpha

def __call__(self, x: np.ndarray, index: Optional[np.ndarray] = None) -> np.ndarray:
if index is not None:
alpha = 1 - np.exp(-np.diff(index, prepend=0) * self.alpha)
else:
alpha = np.ones_like(x) * self.alpha

ewma = np.zeros_like(x)
ewma[0] = x[0]
for i in range(1, len(x)):
ewma[i] = alpha[i] * x[i] + (1 - alpha[i]) * ewma[i - 1]
return ewma

梯度提升树

首先考虑梯度提升树,考虑一个有nn个样本,每个样本有mm个特征的数据集D={(xi,yi)}\mathcal{D} = \{(\mathrm{x}_i, y_i)\},一个集成树模型实际上得到的使用K个具有可加性质的函数,得到的输出对应如下所示:

y^i=ϕ(xi)=k=1Kfk(xi),fkF\hat{y}_i = \phi(\mathrm{x}_i) = \sum_{k=1}^K f_k(\mathrm{x}_i),\quad f_k \in \mathcal{F}

对于每一棵树而言,一个输入会被映射到一个对应的叶节点,这个节点上的权重就对应这个输入的结果。在这里目标函数使用被正则化的形式:

L(ϕ)=il(y^i,yi)+KΩ(fk)\mathcal{L}(\phi) = \sum_{i}l(\hat y_i, y_i) + \sum_K \Omega(f_k)

whereΩ(f)=γT+12λw2\text{where} \quad \Omega(f) = \gamma T + \frac12 \lambda \|w\|^2

其中前半部分ll代表的是损失函数,用来量化预测值与真实值之间的差距,后者是正则化项,用来控制模型的复杂度,防止过拟合。对于树模型而言,正则化项的第一项控制叶节点的数量,后一项控制每个叶节点的权重。如果去掉正则化项,实际上就是普通的梯度提升树。

在对于第tt颗树的时候,我们需要优化的目标函数实际上以下式子:

L(t)=i=1nl(yi,y^i(t1)+ft(xi))+Ω(ft)\mathcal{L}^{(t)} = \sum_{i=1}^n l(y_i, \hat y_i^{(t-1)} + f_t(\mathrm{x}_i)) + \Omega(f_t)

将损失函数展开到二阶近似:

L(t)i=1n[l(yi,y^i(t1))+gift(xi)+12hift2(xi)]+Ω(ft)\mathcal{L}^{(t)} \approx \sum_{i=1}^n \left[ l(y_i, \hat y_i^{(t-1)}) + g_i f_t(\mathrm{x}_i) + \frac12 h_i f_t^2(\mathrm{x}_i)\right] + \Omega(f_t)

其中y^i(t1)\hat y_i^{(t-1)}是前t1t-1颗树的结果,在当前的优化实际上是一个常数,将其移除之后得到tt步的优化函数为:

L~(t)=i=1n[gift(xi)+12hift2(xi)]+Ω(ft)\tilde{\mathcal{L}}^{(t)} = \sum_{i=1}^n \left[ g_i f_t(\mathrm{x}_i) + \frac12 h_i f_t^2(\mathrm{x}_i) \right] + \Omega(f_t)

定义Ij={jq(xi)=j}I_j = \{j| q(\mathrm{x}_i) = j\} 为叶节点jj上面对应的样本集,于是可以修改求和形式如下:

L~(t)=i=1n[gift(xi)+12hift2(xi)]+Ω(ft)=j=1TiIj[gift(xi)+12hift2(xi)]+γT+12λj=1Twj2=j=1TiIj[giwj+12hiwj2]+γT+12λj=1Twj2=j=1T[(iIjgi)wj+12(iIjhi+λ)wj2]+γT\begin{aligned} \tilde{\mathcal{L}}^{(t)} &= \sum_{i=1}^n \left[ g_i f_t(\mathrm{x}_i) + \frac12 h_i f_t^2(\mathrm{x}_i) \right] + \Omega(f_t) \\ &= \sum_{j=1}^T \sum_{i \in I_j} \left[ g_i f_t(\mathrm{x}_i) + \frac12 h_i f_t^2(\mathrm{x}_i) \right] + \gamma T + \frac12 \lambda \sum_{j=1}^T w_j^2 \\ &= \sum_{j=1}^T \sum_{i \in I_j} \left[ g_i w_j + \frac12 h_i w_j^2 \right] + \gamma T + \frac12 \lambda \sum_{j=1}^T w_j^2 \\ &= \sum_{j=1}^T \left[ \left(\sum_{i \in I_j} g_i\right) w_j + \frac12 \left(\sum_{i \in I_j}h_i + \lambda\right) w_j^2 \right] + \gamma T \end{aligned}

当对于一个确定的树结构,γT\gamma T为常量,前面这一项对应于wjw_j的一个二次表达式,可以得到最优解为:

wj=iIjgiiIjhi+λw_j^\star = - \frac{\sum_{i \in I_j} g_i}{\sum_{i \in I_j}h_i + \lambda}

带入可以知道最优的值为:

L~(t)(q)=12j=1T(iIjgi)2iIjhi+λ+γT\tilde{\mathcal{L}}^{(t)}(q) = - \frac12 \sum_{j=1}^T \frac{(\sum_{i \in I_j} g_i)^2}{\sum_{i \in I_j}h_i + \lambda} + \gamma T

上式仅仅与树结构qq有关,所以可以作为一个树结构的度量,越小说明这个树结构越好。由于没有办法穷举所有可能的树结构,所以只能贪心地对于树结构去改进,增添新的分支。假设我们希望把一个节点分离成两个子集ILI_LIRI_R那么这个分裂会带来的L~\tilde{\mathcal{L}}的减少就是:

Lsplit=LbeforeLafter=12[(iILgi)2iILhi+λ+(iIRgi)2iIRhi+λ(iIgi)2iIhi+λ]γ\begin{aligned} \mathcal{L}_{\text{split}} &= \mathcal{L}_{\text{before}} - \mathcal{L}_{\text{after}} \\ &= \frac12 \left[ \frac{(\sum_{i \in I_L} g_i)^2}{\sum_{i \in I_L}h_i + \lambda} + \frac{(\sum_{i \in I_R} g_i)^2}{\sum_{i \in I_R}h_i + \lambda} - \frac{(\sum_{i \in I} g_i)^2}{\sum_{i \in I}h_i + \lambda} \right] - \gamma \end{aligned}

这个值即为分裂带来的增益,应当越大越好,其中前面一项是因为分裂所带来的提升,后面一项是对于分裂使得模型复杂度增加的惩罚。所以γ\gamma相当于给节点分裂设定了阈值,只有当分裂带来的增益超过这个阈值,才会进行树分裂,起到了剪枝的效果。

节点分裂算法

精确贪心算法

关键问题就是如何找到最优的分裂方案来获得最大的分裂增益,最直观的方法就是进行遍历,只要对于数据所有可能的分裂方式进行一次遍历,就可以从中找到增益最大的分裂方式。为了算法能够执行的更加高效,我们需要在最开始对于数据进行一次排序,这样就只要在有序数据上进行一次遍历就可以了。

image-20220316145334272

近似算法

精确贪心算法由于需要遍历所有的可能,非常消耗时间。并且当数据没有办法全部放进内存的时候,进行精准的贪心算法明显是不可行的,所以需要近似算法。精确贪心算法相当于,对于连续变量当中的所有分隔,都作为分割点的候选。一个很自然的近似算法就是,只从当中选择一个子集作为分割点的候选,就是将连续变量给映射到一个个的桶当中,然后基于这些通的统计信息,来选择分割点。具体算法如下图所示,只需要将每一个桶,作为其中的一个样本来思考就可以了。

image-20220316145351285

那么如何分桶实际上就是近似算法的关键所在,XGBoost的论文当中提出了两种方案:

  • 全局方法(global):即在最开始的构造时间就进行分桶,并且在整个节点分裂的过程当中,都采用最开始的分桶结果。

    • 需要进行更少的分桶操作
  • 局部方法(local):在每次分裂的时候,都重新进行分桶。

    • 每一次都在改进分桶方案,对于更深的树会更好
    • 需要更少的候选数量

当然从结果上来看,当全局方法的候选数量提升之后,也同样可以获取和局部方法差不多的表现。

image-20220320000350194

对于具体如何选择分割点,论文提出了一个叫做Weighted Quantile Sketch的方法。使用Dk={(x1k,h1),(x2k,h2),,(xnk,hn)}\mathcal{D}_k = \{(x_{1k}, h_1), (x_{2k}, h_2) ,\ldots, (x_{nk}, h_n)\}来表示第kk个特征以及样本的二阶梯度,定义一个rank函数为rk:R[0,+)r_k: \mathbb{R} \rightarrow [0, +\infty)如下所示:

rk(z)=1(x,h)Dkh(x,h)Dk,x<zhr_k(z) = \frac{1}{\sum_{(x, h) \in \mathcal{D}_k} h} \sum_{(x, h) \in \mathcal{D}_k, x<z}h

对于之前算法当中所提到的ϵ\epsilon,实际上就是要找到一系列的分割点{sk1,sk2,,skl}\{s_{k1}, s_{k_2}, \ldots , s_{kl}\},使得:

rk(sk,j)rk(sk,j+1)<ϵ|r_k({s_{k, j}}) - r_k (s_{k, j+1})| < \epsilon

所以ϵ\epsilon相当于一个度量采样点数量的值,ϵ\epsilon越小,对应的分割点就越多,数量近似为1/ϵ1 / \epsilon。 而采用二阶梯度作为分割依据的根据,来源于之前的目标函数:

L~(t)=i=1n[gift(xi)+12hift2(xi)]+Ω(ft)=i=1n12hi(ft(xi)gihi)2+Ω(ft)+constant\begin{aligned} \tilde{\mathcal{L}}^{(t)} &= \sum_{i=1}^n \left[ g_i f_t(\mathrm{x}_i) + \frac12 h_i f_t^2(\mathrm{x}_i) \right] + \Omega(f_t) \\ &= \sum_{i=1}^n \frac 12 h_i \left(f_t(\mathrm{x}_i) - \frac{g_i}{h_i}\right)^2 + \Omega(f_t) + \text{constant} \end{aligned}

之前的目标函数,实际上可以看作是一个以hih_i为权重的加权平方损失,所以采用hih_i来计算分割点。对于Weighted Quantile Sketch的具体实现,论文提供了一种新的数据结构,具体在论文附录当中,有兴趣的读者可以自己查看。

XGBoost对于稀疏特征有特殊的优化。稀疏矩阵的产生原因可能是缺失值,又或者One-Hot方法的使用。对于稀疏数据可以做一些特殊处理,我们可以将随意地分到左子树或者右子树,或者可以从数据当中来确定子树的分配方式。

image-20220316152549798

如上述算法所示,只对于对应特征没有缺失值的样本来考虑分割点,而对于所有缺失值,考虑统一分到左边或者统一分到右边。对于两种情况以及所有分割点,取当中能够获得最大增益的作为最终选择的节点分裂方式。

在B站上看到李沐在做AI方面论文的带读,看了一遍讲如何读论文,做个笔记,顺便记录一些自己的想法。

从整体上来看,整个视频基本上可以用下面这一个表来概括:

Pass1 Pass2 Pass3
Title
Abstruction
Introduction
Method
Experiment
Conclusion
  • Pass1
    • 只看标题、摘要和结论
    • 观察和自己的研究方向有没有关联性,适不适合自己的研究方向,确定论文值不值得读
  • Pass2
    • 快速过一遍整个论文,不需要了解所有的细节
    • 关注重要的图和表,看明白Method当中的流程图以及Experimet中图表的对应指标
    • 关注重要的相关工作,如果文章太难,可以先读引用的文献
  • Pass3
    • 关注所有细节
    • 换位思考,体会从提出问题到解决问题的全部过程
    • 寻找之后可能的改进空间

个人观点

李沐所讲的论文阅读方法,更多的是针对于经典论文,知道这是一篇好论文之后该如何去读。事实上,AI论文数量激增,大量论文并不能够提供三遍阅读的知识量。更多的是在第二遍阅读的过程当中,关注提出工作的启发点,以及论文当中是否出现类似数据泄露,对比不公平这样的情况。

PyTorch的Tensor支持非常多的索引方法,从Tensor当中取出一个子矩阵是一个常用的需求,如果是需要取出一个连续子矩阵或者子矩阵的索引是等间距排列的情况,可以直接采用切片索引的方式进行解决。对于更一般的情况,没有特别直接的解决办法。

为方便起见,这里定义数据以及需要取出的子矩阵的行列索引如下,这里设置的索引的行列编号相同。

1
2
3
4
5
6
7
8
9
10
11
>>> data = torch.arange(36).reshape(6, 6)
>>> data
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23],
[24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35]])
>>> idx = torch.LongTensor([1, 4, 5])
>>> idx
tensor([1, 4, 5])

如果直接利用索引进行取值操作,取到的是对角线上的元素

1
2
>>> data[idx, idx]
tensor([ 7, 28, 35])

如果按照先行后列的方法进行取值,可以获得预期的元素,如下所示

1
2
3
4
>>> data[idx][:, idx]
tensor([[ 7, 10, 11],
[25, 28, 29],
[31, 34, 35]])

但是这样取出来的Tensor并不对应原始矩阵当中的子矩阵,而是一个复制,如果在上面进行赋值操作,并不会对原始Tensor进行修改

1
2
3
4
5
>>> data[idx][:, idx] = 0
>>> data[idx][:, idx]
tensor([[ 7, 10, 11],
[25, 28, 29],
[31, 34, 35]])

如果有修改的需求,更加优雅的方式是采用np.ix_方法或torch.meshgrid方法。

np.ix_的示例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
>>> data[np.ix_(idx, idx)]
tensor([[ 7, 10, 11],
[25, 28, 29],
[31, 34, 35]])
>>> data[np.ix_(idx, idx)] = 0
>>> data
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 0, 8, 9, 0, 0],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23],
[24, 0, 26, 27, 0, 0],
[30, 0, 32, 33, 0, 0]])

torch.meshgrid的示例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> x, y = torch.meshgrid(idx, idx)
>>> data[x, y]
tensor([[ 7, 10, 11],
[25, 28, 29],
[31, 34, 35]])
>>> data[x, y] = 0
>>> data
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 0, 8, 9, 0, 0],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23],
[24, 0, 26, 27, 0, 0],
[30, 0, 32, 33, 0, 0]])

VSCode作为一个轻量级、跨平台的代码编辑器,在各种插件的支持下可以达到媲美IDE的编程开发体验。可以让人在一个编辑器中完成各种编程语言的开发工作,并且支持远程开发和协同开发。这里记录一些个人关于VSCode的基本设置,便于快速的在一台新的机器上配置好VSCode,并且满足基本的编程需求。相关的插件和配置列表可能会随着时间列表不断进行更新。

外观

字体

  • JetBrains Mono

    • 通过官方下载地址进行下载安装

    • setting.json 中添加

      1
      2
      3
      4
      {
      "editor.fontFamily": "JetBrains Mono",
      "terminal.integrated.fontFamily": "JetBrains Mono"
      }
  • Font Switcher

    • 拓展商店安装

颜色主题

  • One Dark Pro

    • 拓展商店安装
  • file-icons

    • 拓展商店安装
  • Bracket Pair Colorizer

    • 拓展商店安装
  • Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code

    • 拓展商店安装
    • 为VSCode页面提供中文支持
  • Better Comments

    • 拓展商店安装
    • 为注释提供更加丰富的颜色分类
  • Ruler

    • setting.json中添加

      1
      2
      3
      {
      "editor.rulers":[80]
      }
    • 在80个字符的位置提供一条标尺

远程开发

  • Remote -SSH
    • 拓展商店安装

编程相关

  • Code Runner
    • 拓展商店安装

Python

  • Python Extension Pack

    • 拓展商店安装
  • Mypy

    • 拓展商店安装
    • 为Python提供静态类型检查
  • Black

    • setting.json中添加

      1
      2
      3
      4
      {
      "python.formatting.provider": "black",
      "editor.formatOnSave": true,
      }
    • 为Python提供代码格式化

辅助编程

  • GitHub Copilot
    • 拓展商店安装
    • 需要绑定GitHub账号

其他

  • LeetCode
    • 拓展商店安装
  • Docker
    • 拓展商店安装
  • Kubernetes
    • 拓展商店安装

本文仅作为技术分享,希望大家都能够拥有锻炼的自由,但不鼓励使用技术进行跑步打卡

针对北大的校园体育课跑步打卡,已经有如PKUNoRun这样的开源项目,但是PKUNoRun仅仅支持安卓平台的PKURunner用户,使用如乐动力等其他软件作为打卡方式的并不能很方便的使用。

本文采取的思路为进行GPS定位模拟,从而实际生成跑步轨迹。这种方法的优缺点如下:

  • 优点
    • 操作简单,不需要针对APP进行分析和逆向
    • 通用,可以适用于各种打卡平台和系统
  • 缺点
    • 耗时,需要挂机等同于跑步本身的时间

网络上已有的大多数教程普遍基于Xcode进行实现,但是由于Xcode版本更新以及使用存在门槛,很多开源教程的方法存在过时的问题。这里采用开源工具LocationSimulator进行GPS模拟定位,比使用Xcode更加用户友好。关于LocationSimulator的具体安装以及使用可以参考GitHub上的项目主页,总体使用方法即使用数据线连接手机,选中已连接的设备,之后便可以通过GPX文件或者手动控制的方式进行GPS模拟了。

在完成软件安装之后,则需要确定对应的打卡地点的经纬度坐标,可以考虑直接在LocationSimulator结合手机地图软件进行标点,来确定对应位置的经纬度,也可以考虑使用google地图等别的方式。在操场跑道一圈标上足够多的点之后,可以考虑利用经纬度坐标加上轻微的随机化来生成跑步轨迹对应的GPX文件。

以北京大学五四操场作为打卡地点违例,在确定操场一圈的经纬度坐标之后,便可以采用以下代码进行GPX文件的生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import random

print("<?xml version=\"1.0\"?>\n<gpx version=\"1.1\" creator=\"Xcode\">\n")

points = [[39.98670783215012, 116.30664405734012],
[39.98618352533768, 116.30672865002087],
[39.98618352533768, 116.30672865002087],
[39.98581016760418, 116.30678898677019],
[39.98571908922798, 116.30689433671324],
[39.98561288688673, 116.30708452367374],
[39.98565674006142, 116.30742482197985],
[39.98570648307997, 116.30754884871082],
[39.98581008421271, 116.30760844737627],
[39.98630069330874, 116.30760921192963],
[39.98680940636073, 116.30751827280433],
[39.98691331279552, 116.30743465659333],
[39.98701527533597, 116.30723729348047],
[39.98702174059007, 116.30706010182425],
[39.98697385542131, 116.30689805739706],
[39.98687734879176, 116.30675655334059]]

for round in range(100):
for lat, lon in points:
print(" <wpt lat=\"{}\" lon=\"{}\"></wpt>\n".format(
lat + random.random() * 0.00005, lon + random.random() * 0.00005))

print("</gpx>")

之后直接运行Python脚本,将标准输出流重定向到文件,即可得到对应的GPX文件。

1
python gen_gpx.py > circle.gpx

在LocationSimulator通过File -> Open GPX File导入GPX文件,此时GPS坐标就已经在操场上进行规律的运动了。在这个时候打开手机上对应的打卡软件便可以足不出户进行跑步打卡了,同时通过在Walk和Cycle模式当中切换可以调节跑步速度,使得最终的平均速度在打卡范围之内。

P.S.

  1. 为保持LocationSimulator可以持续地对GPS定位进行修改,推荐保持电脑/手机屏幕常亮,避免锁屏。

  2. 由于连接有切断风险,所以LocationSimulator默认在断开链接之后仍然保持虚拟GPS,每次使用完记得点击左上角的X进行退出。

这是关于Optiver Trivia Night的记录(主要关于最开始的Quiz部分),最开始部分为一个大约30min的Quiz,之后为Optiver实习项目相关的介绍和答疑。其中Optiver是一家全球顶级的做市商,具体信息可以参见官网。

Quiz部分的得分与正确率和答题速度相关,在大家都答对题目的情况下,答题更快的可以获得更高的得分。

第一名可以获得Optiver的终面机会,前五名获得AirPods。

题目总量为四十道,总共用时30min,总共分为几类问题:

  • Quick Math (5s) - 基本的数学运算,难度大概在三位数加减法和两位数乘法的水平,重点可能在于时间限制较为紧迫
  • Fun Facts (20s) - 询问一些现实生活当中的数字,考察量级估计和现场搜索的能力
  • Find Pattern (30s) - 图形找规律,填写下一项是什么
  • Easy Brain Teasers (30s) - 较为简单,基本上是基本数学直觉的考察
  • Sequence (60s) - 数列找规律,填写下一项是什么
  • Medium Brain Teasers (60s) - 普通的概率问题
  • Hard Brain Teasers (120s) - 较复杂一些的概率问题

最后排名为Rank 25/150,感觉如果有一定的面试准备的话应该进前十问题不是很大。大部分题目并不会很难,提供足够的时间都能够做的出来,只是在Quiz的情况下会有很强的时间紧迫感。

以下是一部分的题目记录,其中由于Quick Math部分时间过于紧张,所以基本上没有保存到截图。

部分Fun Facts可能会随着时间改变,这里的结果以2021年9月作为参考


No. 7 (Hard Brain Teasers)

10个人围成一个圈。每个人可以拍一拍左侧或者右侧的人,请问被拍的人的个数的期望是多少?

  1. 2.5
  2. 5
  3. 7.5
  4. 8.36

Answer: C

No. 12 (Sequence)

2,3,7,16,65,?2,3,7,16,65,?

序列的下一个数字是什么?

  1. 321
  2. 81
  3. 122
  4. 179
Answer: A

No. 13 (Fun Facts)

以下最接近沪深市场一天的流通量的量级的是(以RMB计价)?

  1. 10b/day (一百亿)
  2. 100b/day (一千亿)
  3. 1t/day (一万亿)
  4. 10t/day (十万亿)

Answer: C

No. 14 (Sequence)

15,29,56,108,208,?15,29,56,108,208,?

序列的下一个数字是什么?

  1. 400
  2. 416
  3. 438
  4. 386
Answer: A

No. 15 (Fun Facts)

世界上最重的猪有多重

  1. 1157kg
  2. 1312kg
  3. 694kg
  4. 866kg
Answer: A

No.16 (Hard Brain Teasers)

有5封不同的信来自5个不同的人。如果我们随机分发信件,没有一个人收到Ta自己的信的概率是多少?

  1. 4/5
  2. 11/30
  3. 7/20
  4. 5/12

Answer: B

No. 17 (Medium Brain Teasers)

地上有一个10cm * 10cm的无限大网格,你随机扔一枚直径为1cm的硬币。问硬币碰到网格线上的概率是多少?

  1. 50%
  2. 25%
  3. 19%
  4. 11%

Answer: C

No. 18 (Find Pattern)

Answer: A

No. 19 (Medium Brain Teasers)

红色柱体的高度是多少?

  1. 8
  2. 10
  3. 20/3
  4. 15
Answer: A

No. 21 (Easy Brain Teasers)

下列的哪一个不是素数?

  1. 257
  2. 289
  3. 157
  4. 349
Answer: B

No. 22 (Medium Brain Teasers)

你有机会可以对数字1~6进行下注,投掷三个骰子。如果你选择的数字出现一次,你获得$1;如果两次,你获得$2;如果三次,你获得$3。你所获得金额的期望是多少?

  1. $0
  2. $0.5
  3. $1
  4. $1/3

Answer: B

No. 23 (Fun Facts)

哪一个整数区间当中包含最多的素数?

  1. [1, 100]
  2. [101, 200]
  3. [201, 300]
  4. [301, 400]

Answer: A

No. 24 (Hard Brain Teasers)

(1+2)3000(1+\sqrt{2})^{3000}小数点后第100100位的数字是多少?

提示:(1+2)3000+(12)3000(1+\sqrt{2})^{3000} + (1-\sqrt{2})^{3000} 是一个整数

  1. 0
  2. 1
  3. 4
  4. 9

Answer: D

No. 25 (Medium Brain Teasers)

你站在地球的表面上,你往南走1英里,往西走1英里,往北走1英里,最后你停留在了你开始的地方。地球上有多少点可以让你完成以上的行为?

  1. 0
  2. 1
  3. 2
  4. 无穷多个

Answer: D

No. 26 (Sequence)

91,85,94,83,97,81,?,7991,85,94,83,97,81,?,79

序列的下一个数字是什么?

  1. 80
  2. 100
  3. 102
  4. 95

Answer: B

No. 27 (Hard Brain Teasers)

考虑一个平面上的5个点,在所有的点之间两两连线,你拥有了x条直线。那么以下哪个不可能是x的值?

  1. 5
  2. 6
  3. 7
  4. 8

Answer: C

No. 28 (Easy Brain Teasers)

一个立方体的面、棱、顶点数的总和是多少?

  1. 26
  2. 28
  3. 30
  4. 24
Answer: A

No. 29 (Sequence)

99,18,36,9,1899,18,36,9,18

序列的下一个数字是什么?

  1. 36
  2. 27
  3. 9
  4. 3

Answer: C

No. 31 (Fun Facts)

一个标准的奥林匹克游泳池(25m * 50m * 2m) 有多少升水?

  1. 250
  2. 25000
  3. 2500000
  4. 250000000
Answer: C

No. 34 (Hard Brain Teasers)

一个密码只能以字母而不能以数字开头,那么po88p881有多少种排列可以是密码?

  1. 315
  2. 420
  3. 525
  4. 840
Answer: A

No. 35 (Fun Facts)

火星的平均温度是多少?

  1. -100°C
  2. -60°C
  3. -20°C
  4. 20°C
Answer: B

No. 36 (Medium Brain Teasers)

考虑一个时钟,一天当中时针和分针会重合多少次?

  1. 22
  2. 23
  3. 24
  4. 26

Answer: A

No. 37 (Easy Brain Teasers)

如果下列命题有正确的话,哪一个命题是正确的?

  1. 在列表中至少有1个命题是错误的
  2. 在列表中至少有2个命题是错误的
  3. 在列表中至少有3个命题是错误的
  4. 在列表中至少有4个命题是错误的
Answer: A

No. 38 (Sequence)

3,3,27,69,129,?-3,3,27,69,129,?

序列的下一个数字是什么?

  1. 178
  2. 207
  3. 312
  4. 268
Answer: B

No. 39 (Find Pattern)

Answer: B

No. 40 (Hard Brain Teasers)

一个国际象棋棋盘的每个小正方形边长都为2。请问在棋盘上作一个圆,能够使得整个圆的圆周都在黑色方形内部的最大的圆半径为多少?

  1. Sqrt(2)
  2. 2
  3. Sqrt(10)
  4. Sqrt(34)

Answer: C

所谓的72规则是一个用来在已知年化收益的情况下,估计投入资金实现翻倍需要年数的估算公式,公式表示如下:

Years to Double=72Interest Rate\text{Years to Double} = \frac{72}{\text{Interest Rate}}

首先通过homebrew安装rustup管理工具:

1
$ brew install rustup

安装完后发现并不能够找到rustup指令,通过brew list进行查询,发现实际上安装的为rustup-init,于是再在命令行执行:

1
$ rustup-init

顺着流程安装完成之后,重启终端便可以安装好rust环境以及相关的工具链。

可以查看对应rustc以及cargo的版本:

1
2
$ rustc --version
rustc 1.53.0 (53cb7b09b 2021-06-17)
1
2
$ cargo --version
cargo 1.53.0 (4369396ce 2021-04-27)

简单的创建一个rust语言版的Hello World进行测试:

1
2
3
fn main() {
println!("Hello World!");
}

将其保存为hello.rs

在命令行使用rustc将其编译为可执行文件:

1
$ rustc hello.rs

之后直接执行可执行文件,便可以在终端看到对应的输出了!

1
2
$ ./hello
Hello World!