(FM) Factorization Machines

Factorization Machines(FM) 由日本 Osaka University 的 Steffen Rendle [1] 在 2010 年提出,是一种常用的因子机模型。

FM

假设现在有一个电影评分的任务,给定如下如所示的特征向量 x(包括用户名、当前在看的电影、已经打分的电影、时间特征、之前看的电影),预测用户对当前观看电影的评分。

电影评分

作者在线性回归模型的基础上,添加交叉项部分,用来自动组合二阶特征。

y^(x):=w0+i=1nwixi+i=1nj=i+1nvi,vjxixj\hat y(x):= w_0 + \sum_{i=1}^{n} w_ix_i + \sum_{i=1}^n \sum_{j=i+1}^n \left \langle v_i,v_j \right \rangle x_ix_j

其中交叉特征的权重由两个向量的点积得到,可以解决没有在模型中出现的特征组合权重问题,以及减少参数数量。

Wi,j=vi,vj=f=1kvi,fvj,fW_{i,j}=\left \langle v_i,v_j \right \rangle = \sum_{f=1}^kv_{i,f} \cdot v_{j,f}

通过下面的方法来化简交叉项权重计算,算法复杂度降到线性。

i=1nj=i+1nvi,vjxiyi=12f=1k((i=1nvi,fxi)2i=1nvi,f2xi2)\sum_{i=1}^n \sum_{j=i+1}^n \left \langle v_i,v_j \right \rangle x_iy_i = \frac{1}{2}\sum^k_{f=1} \left( \left(\sum_{i=1}^nv_{i,f}x_i \right)^2 - \sum^n_{i=1} v^2_{i,f} x_i^2 \right)

对交叉项部分的求导:

θy^(x)={1, if θ is w0xi, if θ is wixij=1nvj,fxjvi,fxi2,if θ is vi,f\frac{\partial}{\partial \theta} \hat y \left( x \right) = \begin{cases} 1, & \text{ if $\theta$ is $w_0$} \\ x_i, & \text{ if $\theta$ is ${w_i}$} \\ x_i\sum^n_{j=1} v_{j,f}x_j - v_{i,f}x_i^2, &\text{if $\theta$ is ${v_{i,f}}$} \end{cases}

其中 j=1nvj,fxj{\sum^n_{j=1} v_{j,f}x_j}xi{x_i} 无关,可以在计算导数前预处理出来。

FM vs SVM

对于经典的特征组合问题,不难想到使用 SVM 求解。Steffen 在论文中也多次将 FM 和 SVM 做对比。

在考虑 SVM 的 Polynomial kernel 为 K(x,z):=(x,z+1)2{K(\mathbf{x}, \mathbf{z}) :=(\langle\mathbf{x}, \mathbf{z}\rangle+ 1)^{2}},映射

ϕ(x):=(1,2x1,,2xn,x12,,xn22x1x2,,2x1xn,2x2x3,,2xn1xn)\begin{array}{l}{\phi(\mathbf{x}) :=\left(1, \sqrt{2} x_{1}, \ldots, \sqrt{2} x_{n}, x_{1}^{2}, \ldots, x_{n}^{2}\right.} {\sqrt{2} x_{1} x_{2}, \ldots, \sqrt{2} x_{1} x_{n}, \sqrt{2} x_{2} x_{3}, \ldots, \sqrt{2} x_{n-1} x_{n} )}\end{array}

SVM 的公式可以转化为:

y^(x)=w0+2i=1nwixi+i=1nwi,i(2)xi2+2i=1nj=i+1nwi,j(2)xixj\begin{aligned} \hat{y}(\mathbf{x})=w_{0}+\sqrt{2} \sum_{i=1}^{n} w_{i} x_{i}+\sum_{i=1}^{n} w_{i, i}^{(2)} x_{i}^{2} &+\sqrt{2} \sum_{i=1}^{n} \sum_{j=i+1}^{n} w_{i, j}^{(2)} x_{i} x_{j} \end{aligned}

论文中提到一句上面的公式中 wi{w_{i}}wi,i{w_{i,i}} 表达能力类似,我猜这也是为什么 FM 中没有自身交叉项的原因吧。

FM 相比于 SVM 有下面三个特点:

  1. SVM 中虽然也有特征交叉项,但是只能在样本中含有相对应的特征交叉数据时才能学习。但是 FM 能在数据稀疏的时候学习到交叉项的参数。
  2. SVM 问题无法直接求解,常用的方法是根据拉格朗日对偶性将原始问题转化为对偶问题。
  3. 在使用模型预测时,SVM 依赖部分训练数据(支持向量),FM 模型则没有这种依赖。

Rank

FM 用来做回归和分类都很好理解,简单写一下如何应用到排序任务中。以 pairwise 为例。假设排序结果有两个文档 xi{x_i}xj{x_j},显然用户点击文档有先后顺序,如果先点击 xi{x_i},记 label yij=1{y_{ij}=1},反之点击 xj{x_j},label yij=0{y_{ij}=0}。模型需要去预测 y^ij=sigmoid(y^iy^j){\hat y_{ij} = sigmoid(\hat y_i - \hat y_j)}

参考逻辑回归,用最大似然对参数进行估计,得到损失函数为 L=log(1+exp((y^(xi)y^(xj)){L=\log(1+\exp(-(\hat y(x_i)-\hat y(x_j))}。优化过程和前面提到类似。

NFM

NFM 和 AFM 两篇论文是同一个作者写的,所以文章的结构很相近。

FM 模型由于复杂度问题,一般只使用特征二阶交叉的形式,缺少对 higher-order 以及 non-liner 特征的交叉能力。NFM 尝试通过引入 NN 来解决这个问题。

NFM 的结构如下:第一项和第二项是线性回归,第三项是神经网络。神经网络中利用 FM 模型的二阶特征交叉结果做为输入,学习数据之间的高阶特征。与直接使用高阶 FM 模型相比,可以降低模型的训练复杂度,加快训练速度。

y^NFM(x)=w0+i=1nwixi+f(x)\hat{y}_{N F M}(\mathbf{x})=w_{0}+\sum_{i=1}^{n} w_{i} x_{i}+f(\mathbf{x})

NFM 的神经网络部分包含 4 层,分别是 Embedding Layer、Bi-Interaction Layer、Hidden Layers、Prediction Score。

NFM

  • Embedding Layer 层对输入的稀疏数据进行 Embedding 操作。最常见的 Embedding 操作是在一张权值表中进行 lookup ,论文中作者强调他们这一步会将 Input Feture Vector 中的值与 Embedding 向量相乘。
  • Bi-Interaction Layer 层是这篇论文的创新,对 embedding 之后的特征两两之间做 element-wise product,并将结果相加得到一个 k 维(Embeding 大小)向量。这一步相当于对特征的二阶交叉,与 FM 类似,这个公式也能进行化简:

fBI(Vx)=i=1nj=i+1nxivixjvj=12[(i=1nxivi)2i=1n(xivi)2]f_{B I}\left(\mathcal{V}_{x}\right)=\sum_{i=1}^{n} \sum_{j=i+1}^{n} x_{i} \mathbf{v}_{i} \odot x_{j} \mathbf{v}_{j} =\frac{1}{2}\left[\left(\sum_{i=1}^{n} x_{i} \mathbf{v}_{i}\right)^{2}-\sum_{i=1}^{n}\left(x_{i} \mathbf{v}_{i}\right)^{2}\right]

  • Hidden Layers 层利用常规的 DNN 学习高阶特征交叉
  • Prdiction Layer 层输出最终的结果:

y^NFM(x)=w0+i=1nwixi+hTσL(WL(σ1(W1fBI(Vx)+b1))+bL)\begin{aligned} \hat{y}_{N F M}(\mathbf{x}) &=w_{0}+\sum_{i=1}^{n} w_{i} x_{i} +\mathbf{h}^{T} \sigma_{L}\left(\mathbf{W}_{L}\left(\ldots \sigma_{1}\left(\mathbf{W}_{1} f_{B I}\left(\mathcal{V}_{x}\right)+\mathbf{b}_{1}\right) \ldots\right)+\mathbf{b}_{L}\right) \end{aligned}

实验结果:

AFM

AFM(Attentional Factorization Machine), 在 FM 的基础上将 Attention 机制引入到交叉项部分,用来区分不同特征组合的权重。

y^AFM(x)=w0+i=1nwixi+pTi=1nj=i+1naij(vivj)xixj\hat{y}_{A F M}(\mathbf{x})=w_{0}+\sum_{i=1}^{n} w_{i} x_{i}+\mathbf{p}^{T} \sum_{i=1}^{n} \sum_{j=i+1}^{n} a_{i j}\left(\mathbf{v}_{i} \odot \mathbf{v}_{j}\right) x_{i} x_{j}

单独看上面公式中的第三项结构:

  • Embedding Layer 与 NFM 里面的作用一样,转化特征。
  • Pair-wise Interaction Layer 是将特征两两交叉,如果对这一步的结果求和就是 FM 中的交叉项。
  • Attention 机制在 Attention-based Pooling 层引入。将 Pair-wise Interaction Layer 中的结果输入到 Attention Net 中,得到特征组合的 score aij{a_{i j}^{\prime} },然后利用 softmax 得到权重矩阵 aij{a_{ij}}

aij=hTReLU(W(vivj)xixj+b)aij=exp(aij)(i,j)Rxexp(aij)\begin{aligned} a_{i j}^{\prime} &=\mathbf{h}^{T} \operatorname{Re} L U\left(\mathbf{W}\left(\mathbf{v}_{i} \odot \mathbf{v}_{j}\right) x_{i} x_{j}+\mathbf{b}\right) \\ a_{i j} &=\frac{\exp \left(a_{i j}^{\prime}\right)}{\sum_{(i, j) \in \mathcal{R}_{x}} \exp \left(a_{i j}^{\prime}\right)} \end{aligned}

  • 最后将 Pair-wise Interaction Layer 中的二阶交叉结果和权重矩阵对应相乘求和得到 AFM 的交叉项。

和前一节的实验结果对比,AFM 效果比 NFM 要差一些。这大概就能说明为什么论文中提到 NFM,但是最后没有把 NFM 的结果贴出来,实在是机智。又回到,发论文是需要方法有创新,还是一味追求 state-of-the-art。

参考资料


(Wide&Deep) Wide & Deep Learning for Recommender Systems

背景

这是一篇推荐系统相关的论文,场景是谷歌 Play Store 的 App 推荐。文章开头,作者点明推荐系统需要解决的两个能力: memorization 和 generalization。

memorization 指的是学习数据中出现过的组合特征能力。最常使用的算法是 Logistic Regression,简单、粗暴、可解释性强,而且会人工对特征进行交叉,从而提升效果。但是,对于在训练数据中没有出现过的特征就无能为力。

generalization 指的是通过泛化出现过特征从解释新出现特征的能力。常用的是将高维稀疏的特征转换为低维稠密 embedding 向量,然后使用 fm 或 dnn 等算法。与 LR 相比,减少特征工程的投入,而且对没有出现过的组合有较强的解释能力。但是当遇到的用户有非常小众独特的爱好时(对应输入的数据非常稀疏和高秩),模型会过度推荐。

综合前文 ,作者提出一种新的模型 Wide & Deep。

模型

从文章题目中顾名思义,Wide & Deep 是融合 Wide Models 和 Deep Models 得到,下图形象地展示出来。

Wide & Deep Models

Wide Component 是由一个常见的广义线性模型:y=wTx+b{y=w^Tx+b}。其中输入的特征向量 x{x} 包括两种类型:原始输入特征(raw input features)和组合特征(transformed features)。

常用的组合特征公式如下:

ϕk(x)=i=1dxicki,cki{0,1}{\phi_k(x)=\prod_{i=1}^dx_i^{c_{ki}},c_{ki}\in\{0,1\}}

cki{c_{ki}} 代表对于第k个组合特征是否包含第i个特征。xi{x_i}是布尔变量,代表第i个特征是否出现。例如对于组合特征 AND(gender=female, language=en) 当且仅当 x 满足(“gender=female” and “language=en”)时,ϕk(x)=1{\phi_k(x)=1}

Deep Component 是一个标准的前馈神经网络,每一个层的形式诸如:a(l+1)=f(W(l)a(l)+b(l)){a^{(l+1)}=f(W^{(l)}a^{(l)} + b^{(l)})}。对于输入中的 categorical feature 需要先转化成低维稠密的 embedding 向量,再和其他特征一起喂到神经网络中。

对于这种由基础模型组合得到的新模型,常用的训练形式有两种:joint training 和 ensemble。ensemble 指的是,不同的模型单独训练,且不共享信息(比如梯度)。只有在预测时根据不同模型的结果,得到最终的结果。相反,joint training 将不同的模型结果放在同一个损失函数中进行优化。因此,ensmble 要且模型独立预测时就有有些的表现,一般而言模型会比较大。由于 joint training 训练方式的限制,每个模型需要由不同的侧重。对于 Wide&Deep 模型来说,wide 部分只需要处理 Deep 在低阶组合特征学习的不足,所以可以使用简单的结果,最终完美使用 joint traing。

预测时,会将 Wide 和 Deep 的输出加权得到结果。在训练时,使用 logistic loss function 做为损失函数。模型优化时,利用 mini-batch stochastic optimization 将梯度信息传到 Wide 和 Deep 部分。然后,Wide 部分通过 FTRL + L1 优化,Deep 部分通过 AdaGrad 优化。

实验

本篇论文选择的实验场景是谷歌 app 商店的应用推荐,根据用户相关的历史信息,推荐最有可能会下载的 App。

使用的模型如下:
Wide & Deep model structure for apps recommendation.

一些细节:

  • 对于出现超过一定次数的 categorical feature,ID 化后放入到模型中。
  • Continuous real-valued features 通过 cumulative distribution function 归一化到 [0, 1] 区间。
  • categorical feature 由 32 维 embedding 向量组成,最终的输入到 Deep 部分的向量大概在 1200 维。
  • 每天在前一天 embedding 和模型的基础上进行增量更新。

实验结果:

实验结果

Wide & Deep 模型相对于其他两个模型毫无疑问有提升。但结果中也一个反常的现象:单独使用 Deep 模型离线 AUC 指标比单独使用 Wide 模型差,但是线上对比实验时却有较大的提升。论文中作者用了一句:线下实验中的特征是固定的,线上实验会遇到很多没有出现过的特征组合,Deep 相对于 Wide 有更好的模型泛化能力,所以会有反常现象。由于笔者工作中不关注 AUC,也没有办法继续分析。

总结

作者从推荐系统的的 memorization 和 generalization 入手,设计出新的算法框架。通过线上和线下实验实验,证明 Deep 和 Wide 联合是必须的且有效的。最终也在自己的业务场景带来提升。

Reference


Best of iPhone 2019 软件清单

一直想做一个推荐软件的系列文章,不过完成 2017 年的iPhone软件清单 后就没有动力……很多时候在思考,自己为什么一定要使用 iPhone?iOS 中的软件正是最好的答案,让每一个人享受科技带来的快乐。

这一篇文章和 2017 年的形式一样,删除常用的软件,推荐一些我认为有趣的软件。自从 iOS 12 中引入屏幕时间,今年的推荐顺序就按照屏幕时间中的排序。

目前使用设备:iPhone XR & Apple Watch Series 4

  • Inoreader:RSS 订阅服务商以及 RSS 阅读器。随着这两年对推荐算法相关的新闻阅读器批判,RSS 大有一股复新之势。可惜的是在去年年底左右,inoreader 中每个免费帐号只能关注 150 个订阅源。目前,我通过使用多个帐号来临时解决。
  • 脉脉:职场社交软件。有匿名区以及公司圈,定期查看公司内部的吐槽和爆料。用 Leader 的话来说,整天操 M6 的心。
  • Keep/Nike Running/健身记录:锻炼相关 App 集合。Keep 和 Apple Watch 的配合太差,很多数据不能同步。自带的健身记录展示数据不够详细。Nike Running 不太用。
  • Overcast:免费且功能强大的播客软件,良心到只有少量的播客相关广告。喜欢智能播放和人声增强功能。最近,也支持章节播放功能。
  • AutoSleep:配合 Apple Watch 实现睡眠监控功能。原来大致为读取 Apple Watch 写入健康中的数据判断是否入睡以及睡眠质量。由于硬件的限制,很多时候还是不太准确。不过我也和网上说的一样,每天起来需要看这软件中的评分,判断昨天睡的如何……
  • Kindle:管它微信阅读如何火爆,不忘初心依旧选择 Kindle。配合 Kindle Unlimted,可以阅读很多好书。
  • Google 相册:免费强大的照片管理软件,允许谷歌优化照片质量的后,可以无限存储。
  • Quantumult:梯子软件,美区下载,比小火箭好用。
  • OmniFoucs:GTD 软件,居然使用的正版……
  • Anki:多年之后,我的摘抄终于有了存放之地。
  • Day One:日记软件,喜欢里面的去年今天功能,陆续把日记导入中。
  • Pocket:稍后读。
  • 1 Password:密码软件,全平台通用。
  • 熊猫吃短信:垃圾短信过滤,可以自己配置规则把公司的报警短信给屏蔽了。
  • Ulysses:Markdown 软件,iOS 版也包含在 SetApp 订阅。

在与 2017 版相比,有两个趋势:1. 减少很多与学习强相关的软件。 2. 越来越多的付费或者订阅制软件。期待明年。


Practical Lessons from Predicting Clicks on Ads at Facebook(gbdt + lr)

**主题:**Facebook 2014 年发表的广告点击预测文章。最主要是提出经典 GBDT+LR 模型,可以自动实现特征工程,效果好比于人肉搜索。另外,文章中还给出一个 online learning 的工程框架。

问题:

  • GBDT 如何处理大量 id 类特征
    • 广告类对于 user id 的处理:利用出现的频率以及转化率来代替
    • id 特征放在 lr 中处理。
  • GBDT+LR 和 RF+LR 的区别
    • 选出能明显区分正负样本的特征的变换方式,转换成 one hot 有意义
    • RF + LR 可以并行训练,但是 RF 中得到的区分度不高

收获:

  • 数据支撑去做决策,收获和实验数量成正比。
  • CTR click through rate,点击率
  • 评价指标:
    • Normalized Entropy:越小模型越好
    • Calibration:预测点击数除以真实点击数
    • AUC 正样本出现在负样本前面的概率。
  • 数据新鲜度:模型天级训练比周级训练在 NE 下降 1%。
  • GBDT 和 LR 模型采用不同的更新频率,解决训练耗时不同。但是 GBDT 重新训练之后,LR 必须要重新训练。

网络:

GBDT + LR

利用 GBDT 模型进行自动特征组合和筛选,然后根据样本落在哪棵树哪个叶子生成一个 feature vector 输入到 LR 模型中。这种方法的有点在于两个模型在训练过程从是独立,不需要进行联合训练。

GBDT 由多棵 CART 树组成,每一个节点按贪心分裂。最终生成的树包含多层,相当于一个特征组合的过程。根据规则,样本一定会落在一个叶子节点上,将这个叶子节点记为1,其他节点设为0,得到一个向量。比如下图中有两棵树,第一棵树有三个叶子节点,第二棵树有两个叶子节点。如果一个样本落在第一棵树的第二个叶子,将它编码成 [0, 1, 0]。在第二棵树落到第一个叶子,编码成 [1, 0]。所以,输入到 LR 模型中的向量就是 [0, 1, 0, 1, 0]

Online Learning

文章中提到的 Online Learning 包括三个部分:

  • Joiner 将每次广告展示结果(特征)是否用户点击(标签) join 在一起形成一个完成的训练数据;
  • Trainer 定时根据一个 small batch 的数据训练一个模型;
  • Ranker 利用上一个模块得到模型预测用户点击。

注意的点:

  • waiting window time:给用户展示广告之后,我们只能知道用户点击的广告,也就是模型中的正样本。负样本需要设置一个等待时间来判断,即超过某一个时间没有观测到用户点击某一个广告,就认为这是一个负样本。另外设置这个时间也是一个技术活,时间过短导致click没有及时join到样本上,时间太长数据实时性差以及有存储的压力。最后,无论如何都会有一些数据缺失,为了避免累积误差,需要定期重新训练整个模型。
  • request ID:人家的模型是分布式架构的,需要使用 request ID 来匹配每次展示给用户的结果以及click。为了实现快速匹配,使用 HashQueue 来保存结果。
  • 监控:避免发生意向不到的结果,导致业务损失。我们的实时模型也在上线前空跑了好久。

实验:

有无 GBDT 特征对比

训练两个 LR 模型,一个模型输入样本经过 GBDT 得到的特征,另外一个不输入。混合模型比单独 LR 或 Tree

学习率选择

5 种学习率,前三个每一个特征设置一个学习率,最后两种全局学习率。

结果:应该给每一个特征设置一个不同的学习率,而且学习率应该随着轮次缓慢衰减。

GBDT 参数相关实验

  • 前面的树会带来大量的收益,但是树越多训练越慢。
  • 特征重要程度,累加不同树上某个特征的得分减少贡献。
  • 两种特征:
    • 上下文,冷启动的时候比较重要,与数据新鲜度有关。
    • 历史史特征,权重比较大,关键在于长时间积累。

采样

训练数据大多,需要进行采样。

  • uniform subsampling :无差别采样。使用 10 % 的样本,NE 减少 1 %

  • negative down subsampling :对负样本进行下采样。但不是负采样率越低越好,比如下面的图中0.0250就可能是解决了正负样本不平衡问题。最后的CTR指标结果需要重新进行一次映射。

Reference


「Rime 鼠须管」小鹤双拼配置指南

引言

如何将汉字输入到计算机中是一个编码有关的问题,目前市面上主流的方案包括音码、形码、音形码。和大多数人一样,之前我一直使用全拼,而且得益于 NLP 技术发展,使用搜狗输入法搭配云词库,输入效率可以媲美五笔输入法。

但是今天要和大家分享,是从年初开始使用的全新音码输入方案——小鹤双拼。最初关于双拼的概念来自李笑来《把时间当作朋友》:

在很长的一段时间里,我常言之凿凿地对同学们说“练习打字完全是浪费时间。”我当时的逻辑是这样的。首先,我认为王码五笔字型输入法是给打字员用的。为什么要学它?难道你将来想要当个打字员?我总觉得五笔字型知识一种抄写输入法,因为用他输入时只能边看边打。而对真正创造内容的人来说,先用纸和笔写出来再录入电脑,还有比这更荒诞的事情吗?学习拆字学法已经很累人了,还要练什么指法,见鬼。更不用说这种所谓的输入法对思考的干扰——不仅要把字拆开再输入,还要按照莫名其妙的方法拆字。其次,盲打。我现在不是盲打,只是两根手指输入速度就已经很快了(至少比手写快)。

这样看来,我还有必要学习什么五笔字型和盲打吗?

在我有了这些定见很久之后,发生了一件事情。

那是在1997年,我25岁。当时互联网除了聊天室和论坛,几乎没有什么实际的应用。适逢windows捆绑了哈尔滨工业大学开发的“微软拼音输入法1.0”,某天下午,当我在网上和一位永远都不会知道是谁的女生放肆地聊了两个小时之后,突然发现自己竟已无师自通地学会了所谓的“盲打”了!在这之后的一段时间里,我身边甚至很多人羡慕我打字的速度。为了让自己的打字速度再快一点,我索性花了差不多20分钟,把原本默认的“全拼输入”改成了“”双拼输入“。而这还远远不够。后来,我增设了”慢放模糊音“(不区分z/zh、c/ch、s⇧),又把打字速度提高了一些。这时我第一次意识到‘有些认识,哪怕是简单的常识,也需要亲身经历后才能真正体会”。只有拥有无与伦比的打字速度,才会体会打字速度快的好处。

打字速度提升后,我发现自己不再讨厌在读书的时候做笔记了,因为在键盘上敲字相对于笔写字来说轻松太多。我开始大段地纪录感悟,有时甚至干脆整篇摘抄原文!

李笑来思考的问题正是如何利用最小的代价快速的输入文字。这里代价包括两个方面,输入方案的学习成本以及输入文字的速度。简单分析一下,主流输入法需要用户在键盘上敲击一些字符,然后映射到汉字。就是说输入文字的速度和两个因数有关:敲击键盘的次数(对应计算机中的码长)以及重码的字符数量(对于拼音输入法来说,这里指的是每个拼音对应多少的字)。

从这两个指标来说,五笔应该是接近输入法的极限,五次敲击键盘肯定能选定你需要的汉字。但是学习五笔需要记忆大量的说是有序其实没有太多规律的字根,学习曲线不是一般的陡峭。几年前,自己尝试跟着网上的视频教程学习,最后还是太复杂而放弃。

这时候要介绍一下双拼输入法,它是一种基于全拼的激进改良版拼音输入法。简单来说,它将一个汉字的拼音分成声母和韵母两个部分,输入一个拼音只需要按两个键(减少键盘的敲击次数但是没有减少重码率)。比如对于“拼音”两个字来说,全拼需要输入 pinyin 六个字符,换成是双拼,只需要输入 4 个字符 pbyb。具体介绍可以看 做少数派中的少数派:双拼输入快速入门

由于双拼有声母和韵母,需要先从键盘上进行一次映射,所以有很多的输入方案。我自己入门使用的是小鹤双拼,也推荐大家使用这个方案。主要原因有两点:声母和韵母全部放在字母键上(微软双拼中要用到 ; 键,以及 iOS12 和 MacOS 中自带的输入法都支持这个方案(国内那几个流氓输入法更不用提)。

下图是一张小鹤双拼的输入法键位图,其中黑色的字符代表是键盘上的什么键,棕色的代表这个键表示声母时是什么,蓝色的代表这个键表示韵母时是什么。学习双拼的过程也很简单,拼音本身就会,无非是熟悉键位。从我自己的角度来说,每天抽出几分看一下键位图,再在 双拼练习 @ BlueSky (相关的介绍可以看:快速上手双拼,可以尝试这个练习平台 - 少数派)网站上练习 5 分钟,一周后可以完全脱离键位图来打字,之后就是孰能生巧的过程。

说回来当你学习了这么强大的内功之后,自然需要神兵来辅助你输入。在饱受国内的流氓输入法侵害之后(比如输入到一半给你跳一个什么斗图功能提示),我遇到今天的主角 RIME | 中州韻輸入法引擎,它是由佛振开发的一种开源输入框架,业内人士称之为「神级输入法」。上一个有类似拉风的称号的软件还是「神级编辑器」—— Vim。Rime 有趣的一点是在不同的平台上有不同的名字,包括 Linux 上的「中州韵」,Win 上的「小狼毫」以及 Mac 上的「鼠须管」。稍微有文化的人可以反应过来,后面两个正是两种不同的毛笔名字。

简单总结一下为什么要使用鼠须管:一是安全,不会出现什么输入法读取你个人信息更甚者是密码发送到服务器,也不知道他们用来机器学习什么;二是配置全平台同步,解决多台设备的输入法配置问题;三是快,不会出现输入法跟不上我的打字速度而导致思路中断的情况。

安装

下載及安裝 | RIME | 中州韻輸入法引擎 主页,你可以找到对应不同平台的安装方法。

对于 MacOS 来说,最简单的方式莫过于在终端中输入 brew cask install squirrel 安装软件本体。如何你想使用 rime/plum: 東風破可以在终端中输入 curl -fsSL https://git.io/rime-install | bash -s -- :preset double-pinyin 。其中 preset double-pinyin 指定下载时默认包括小鹤双拼输入方案。

配置

防止配置时候出现各种意想不到的情况,首先推荐阅读官方文档 CustomizationGuide · rime/home Wiki

Rime 的配置文件默认放在 ~/Library/Rime,而且是一种扩展 yaml 文件。默认的文件名为 .schema.yaml,比如小鹤双拼相关的默认配置在 double_pinyin_flpy.schema.yaml 中。如果我们自己想添加一些设置,推荐写在以.custom.yaml 结尾的新文件中,比如 double_pinyin_flypy.custom.yaml

default.custom.yaml

这个文件写一些全局的配置。

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
patch:
switcher:
caption: 〔方案选单〕
hotkeys: Control+grave
# 候选词数量
menu:
page_size: 9
# 使用的输入方案
schema_list:
- schema: luna_pinyin_simp
- schema: luna_pinyin
- schema: double_pinyin_flypy
# 输入法中英文状态快捷键
ascii_composer/switch_key:
Caps_Lock: commit_code
Control_L: noop
Control_R: noop
# 按下左 shift 英文字符直接上屏,不需要再次回车,输入法保持英文状态
Shift_L: noop
Shift_R: noop
# 在一些软件中默认使用英文输入状态
app_options:
com.apple.finder: &a
ascii_mode: true
no_inline: true
com.googlecode.iterm2: *a
com.alfredapp.Alfred: *a
com.runningwithcrayons.Alfred-2: *a
org.vim.MacVim: *a
com.apple.Terminal: *a

最后在修改配置时,可以查阅 Rime_collections/Rime_description.md at master · LEOYoon-Tsaw/Rime_collections 寻找相关信息。

installation.yaml

配置文件多平台同步相关文件,sync_dir 指定同步文件夹的位置,配合例如坚果云之类的软件实现备份同步。

1
2
3
4
5
6
7
8
distribution_code_name: Squirrel
distribution_name: "鼠鬚管"
distribution_version: 0.11.0
install_time: "Sun Dec 23 23:42:01 2018"
installation_id: "mac_didi"
sync_dir: "/Users/didi/Documents/rime_sync"
rime_version: 1.4.0
update_time: "Mon Jun 3 07:18:30 2019"

自由输入法RIME简明配置指南 - 少数派 中了解到,rime 个人词典双向同步,用户配置单向同步。另外需要把配置文件上传到 git 中,方便不同设备使用。

double_pinyin_flypy.custom.yaml

小鹤双拼相关的自用配置

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
37
38
39
40
41
42
43
44
45
46
47
48
49
patch:
# 引用 `symbols.custom` 文件里面的符号
# 'punctuator/import_preset': symbols.custom
'recognizer/patterns/punct': "^/([a-z]+|[0-9])$"
# 載入朙月拼音擴充詞庫
"translator/dictionary": ryen
# 更改‘西文’为‘英文’,‘增广’为‘扩展集’
punctuator:
import_preset: symbols.custom
half_shape:
"#": "#"
"`": "`"
"~": "~"
"@": "@"
"=": "="
"/": ["/", "÷"]
'\': ["、", '\']
"'": {pair: ["「", "」"]}
"[": ["【", "["]
"]": ["】", "]"]
"$": ["¥", "$", "€", "£", "¢", "¤"]
"<": ["《", "〈", "«", "<"]
">": ["》", "〉", "»", ">"]
switches:
- name: ascii_mode
reset: 0
states: ["中文", "英文"]
- name: full_shape
states: ["半角", "全角"]
- name: zh_simp
reset: 1
states: ["漢字","汉字"]
- name: ascii_punct
states: [",。", ",."]
- name: extended_charset #生僻字开关
states: ["通用", "扩展集"]
- name: show_emoji # 该项为表情输入,具体内容可见下文中 [关于表情输入] 部分
reset: 1
states: [ "🈚️️\uFE0E", "🈶️️\uFE0F" ]
# 输入双拼码的时候不转化为全拼码
translator/preedit_format: {}
simplifier:
option_name: zh_simp
# 分号上屏二候选词;引号上屏三候选词
"key_binder/bindings":
- { when: has_menu, accept: semicolon, send: 2 }
- { when: has_menu, accept: apostrophe, send: 3 }
- { when: paging, accept: bracketleft, send: Page_Up }
- { when: has_menu, accept: bracketright, send: Page_Down }

squirrel.custom.yaml

自定义皮肤相关文件

1
2
3
4
5
6
7
8
9
10
11
patch:
style:
color_scheme: psionics
horizontal: true
inline_preedit: true
candidate_format: "%c\u2005%@ \u2005" # 用 1/6 em 空格 U+2005 来控制编号 %c 和候选词 %@ 前后的空间。
font_point: 16 # 候选文字大小
label_font_point: 14 # 候选编号大小
corner_radius: 5 # 候选条圆角
border_height: 0 # 窗口边界高度,大于圆角半径才生效
border_width: 0 # 窗口边界宽度,大于圆角半径才生效

最后,我的配置在 xiang578/rime 同步。

调试

Debug 是折腾 rime 不得不面对的一步,主要方式是查看 rime 部署的时产生的 log 文件。对于鼠鬚管而言, log 文件保存在 $TMPDIR/rime.squirrel.* 中。

  • 在命令行中输入 echo $TMPDIR 获得路径
  • 将地址输入到 「访达-前往-前往文件夹…」中跳转
  • 右键点击 rime.squirrel.WARNING 文件,选择显示原身,并使用文本编辑器打开

文件格式类似于下面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Log file created at: 2019/06/07 23:31:54
Running on machine: didideMiBook-Pro.local
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
W0607 23:31:54.549346 162086912 config_compiler.cc:391] inaccessible node: punctuation.custom:/patch
W0607 23:31:54.554167 162086912 config_compiler.cc:391] inaccessible node: key_bindings.custom:/patch
W0607 23:31:54.560144 162086912 deployment_tasks.cc:179] schema list not defined.
W0607 23:36:13.133517 161013760 config_compiler.cc:391] inaccessible node: punctuation.custom:/patch
W0607 23:36:13.135843 161013760 config_compiler.cc:391] inaccessible node: key_bindings.custom:/patch
W0607 23:36:13.149406 161013760 config_data.cc:62] nonexistent config file '/Users/didi/Library/Rime/luna_pinyin_simp.custom.yaml'.
W0607 23:36:13.154920 161013760 config_compiler.cc:391] inaccessible node: punctuation.custom:/patch
W0607 23:36:13.156643 161013760 config_compiler.cc:391] inaccessible node: key_bindings.custom:/patch
W0607 23:36:13.331344 161013760 config_compiler.cc:391] inaccessible node: key_bindings.custom:/patch
W0607 23:36:13.333066 161013760 config_compiler.cc:391] inaccessible node: punctuation.custom:/patch
W0607 23:36:13.668072 161013760 config_data.cc:62] nonexistent config file '/Users/didi/Library/Rime/stroke.custom.yaml'.
W0607 23:36:13.670761 161013760 config_compiler.cc:391] inaccessible node: key_bindings.custom:/patch
W0607 23:36:13.672724 161013760 config_compiler.cc:391] inaccessible node: punctuation.custom:/patch
W0607 23:36:14.281919 161013760 config_compiler.cc:391] inaccessible node: punctuation.custom:/patch
W0607 23:36:14.283246 161013760 config_compiler.cc:391] inaccessible node: key_bindings.custom:/patch

按右 shift 切换输入法

之前使用搜狗输入法时,特别喜欢的一个功能:按右 shift 切换输入法的输入状态,实现暂时切换到英文状态。Rime 作者在 使用 Control 鍵切換中西文,上屏已輸入的編碼;令 Caps Lock 改變字母的大小寫 中提到一种方案。例如下面:

1
2
3
4
5
6
7
8
patch:
ascii_composer/good_old_caps_lock: true
ascii_composer/switch_key:
Caps_Lock: commit_code
Shift_L: noop
Shift_R: commit_code
Control_L: commit_code
Control_R: commit_code

然而这样修改完成之后,不论按哪个 Shift 键,都会切换到英文输入状态。看前面那个网页下面作者与其他人的讨论中发现,鼠须管无法区分 Shift 键。

网上查了一下,简单实现的方法是通过 karabiner 软件来改键。详细步骤可以参考 禁用 Squirrel 英文模式,使用左侧 Shift 切换中英 · rime/squirrel Wiki

Reference


博客折腾记

记录博客修修补补的故事

210123: 博客进入 beta 0.3 时代 我也想学习微信不写更新日志

beta 0.3

beta 0.2

长达 9 个月闲置之后,终于迎来一轮对博客的更新。

  • 本着闭环的原则,将评论系统从 Disqus 替换为基于 Github Issues 的 Gitalk。不过,依旧没有多少留言量。
  • 博客主题从 Next 切换到 fi3ework/hexo-theme-archer: 🏹 A smart and modern theme for Hexo.
  • 添加一个新的固定栏目 「每月分享」,目前正在持续探索具体形式中。
  • 除继续使用百度统计之外,加上 Google Analytics 进行数据分析。

beta 0.1

beta 0

早期对博客进行的相关修改有:

博客专栏

  • 【每周分享】:每周六更新,记录过去一周,我看到值得分享的内容
  • 【月读】:每月更新,推荐本月我阅读的一本书
  • 【数字生活】:我的数字生活实践
  • 【博客公告】:分享与这个博客维护相关的内容

博客记录

  • 本博客采用hexo搭建,使用Even主题
  • 托管:Coding Pages
  • 域名:腾讯云
  • 评论:Disqus
  • 统计:百度统计
  • 图床:七牛
  • 20151006:恢复订阅功能
  • 20160303:开启分类和请我喝一杯咖啡
  • 20160623:重新开启评论和百度统计,与自己和解
  • 20160624:更换主题大道至简Maupassant,增加favicon和apple-touch-icon
  • 20170104:放弃github+hexo,投入vps+wordpress怀抱,依旧使用maupassant主题
  • 20170722:主机系统升级失败,从备份中恢复博客,并采用 Twenty Twelve 主题
  • 20171005:重新投入hexo怀抱,并托管于Coding Pages
  • 20180101:开启功德箱
  • 20180501:PV: 657 | UV: 242
  • 20180528:使用 Travis CI 自动部署

除虫记录

Error: Cannot find module ‘node-sass-magic-importer’

ERROR in Cannot find module ‘node-sass’(已解决) - line - CSDN博客

1
cnpm install node-sass@latest

博客折腾记:hexo-leancloud-counter-security 与标题中的引号冲突

昨天按照 hexo-theme-next/LEANCLOUD-COUNTER-SECURITY.md at master · theme-next/hexo-theme-next 这个文档配置博客阅读次数时,遇到 hexo-leancloud-counter-security 插件的一个冲突。

完成配置使用 hexo -d 时,终端中出现下面的错误提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
 ATAL Something's wrong. Maybe you can find the solution here: http://hexo.io/docs/troubleshooting.html
SyntaxError: Unexpected token h in JSON at position 30
at JSON.parse (<anonymous>)
at /Users/didi/Documents/personal/xiang578.github.io/node_modules/hexo-leancloud-counter-security/index.js:92:42
at arrayEach (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_lodash@4.17.11@lodash/lodash.js:516:11)
at Function.forEach (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_lodash@4.17.11@lodash/lodash.js:9344:14)
at Hexo._callee$ (/Users/didi/Documents/personal/xiang578.github.io/node_modules/hexo-leancloud-counter-security/index.js:83:27)
at tryCatch (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_regenerator-runtime@0.11.1@regenerator-runtime/runtime.js:62:40)
at Generator.invoke [as _invoke] (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_regenerator-runtime@0.11.1@regenerator-runtime/runtime.js:296:22)
at Generator.prototype.(anonymous function) [as next] (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_regenerator-runtime@0.11.1@regenerator-runtime/runtime.js:114:21)
at step (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_babel-runtime@6.26.0@babel-runtime/helpers/asyncToGenerator.js:17:30)
at /Users/didi/Documents/personal/xiang578.github.io/node_modules/_babel-runtime@6.26.0@babel-runtime/helpers/asyncToGenerator.js:28:13
at process._tickCallback (internal/process/next_tick.js:68:7)

看提示貌似是利用 Json 解析字符串的时候出现问题。打开 node_modules/hexo-leancloud-counter-security/index.js:92,对应出现一个解析 JSON的:

1
y = JSON.parse(memoData[memoIdx].substring(0, memoData[memoIdx].length - 1));

js 没有怎么接触过,不知道能不能单步调试之类的,只好祭出输出调试大法,加上两个输出:

1
2
3
console.log(memoIdx)
console.log(memoData[memoIdx])
y = JSON.parse(memoData[memoIdx].substring(0,memoData[memoIdx].length - 1));

然后再执行 hexo -d 命令,命令行输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
28
{"title":"System.out.println("hello world!");","url":"/post/hello-world.html"},
FATAL Something's wrong. Maybe you can find the solution here: http://hexo.io/docs/troubleshooting.html
SyntaxError: Unexpected token h in JSON at position 30
at JSON.parse (<anonymous>)
at /Users/didi/Documents/personal/xiang578.github.io/node_modules/hexo-leancloud-counter-security/index.js:92:42
at arrayEach (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_lodash@4.17.11@lodash/lodash.js:516:11)
at Function.forEach (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_lodash@4.17.11@lodash/lodash.js:9344:14)
at Hexo._callee$ (/Users/didi/Documents/personal/xiang578.github.io/node_modules/hexo-leancloud-counter-security/index.js:83:27)
at tryCatch (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_regenerator-runtime@0.11.1@regenerator-runtime/runtime.js:62:40)
at Generator.invoke [as _invoke] (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_regenerator-runtime@0.11.1@regenerator-runtime/runtime.js:296:22)
at Generator.prototype.(anonymous function) [as next] (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_regenerator-runtime@0.11.1@regenerator-runtime/runtime.js:114:21)
at step (/Users/didi/Documents/personal/xiang578.github.io/node_modules/_babel-runtime@6.26.0@babel-runtime/helpers/asyncToGenerator.js:17:30)
at /Users/didi/Documents/personal/xiang578.github.io/node_modules/_babel-runtime@6.26.0@babel-runtime/helpers/asyncToGenerator.js:28:13
at process._tickCallback (internal/process/next_tick.js:68:7)

JSON 在解析字符串{"title":"System.out.println("hello world!");","url":"/post/hello-world.html"} 时出现错误。对应的正是之前写的一篇名为 System.out.println("hello world!"); 的文章,由于 JSON 格式中字符串是需要用"" 修饰,导致JSON 中出现了一个 "title":"System.out.println("hello world!");" key-value 组合。然而实际上 JSON 只会将 "System.out.println("h 解析成 value,之后出现的 h 被当成非法字符报错。

定位问题之后,暂时修改文章的标题为 hello world! | 算法花园,绕过部署失败。


博客折腾记:主题更新、迁移博客到腾讯云COS以及解决百度收录

本周有空对博客进行新一轮折腾,现在将这些尝试记下来和大家分享。

1. 主题更新

我在 博客折腾记:使用 Travis CI 自动部署 中提到将主题以 modules 的形式加入主仓库。而且现在使用的主题 git 仓库是我自己 fork 的,也有一些修改。几个天之前,hexo-theme-even 的 master 接受 feat: add LaTeX support by JieJiSS · Pull Request #236 ,完成对 LaTeX 公式的支持。所以,我需要将使用的代码和最新的代码合并。

这里使用的是 github Pull request 功能。在你自己 fork 的仓库的网页上点击 new pull request,然后按照下图修改。就会生成一个新的 Pull request 。
-w1009

而且,如果你没有修改过原来的代码,PR 能自动合并。不过由于我对代码做了一些修改,会产生一些冲突,需要手动解决冲突(这里推荐使用 VS code)。出现下图的情况即成功合并两个库。
-w1046

完成 PR 后,进入你站点下面的对应主题目录,使用 git checkout master 切换到主题的 master 分支,使用 git pull origin master 拉取最新的代码。回退到站点目录下,利用 git add 更新。

2. 迁移博客到腾讯云COS

利用腾讯云存储博客的静态文件,并配合使用 CDN 可以加快国内的访问速度。参考 Hexo博客迁移之旅(Coding到腾讯云COS)+ Travis CI持续集成 - 个人文章 - SegmentFault 思否 以及 如何在腾讯云COS部署HEXO博客 - 云+社区 - 腾讯云

记录两个我遇到的坑。

新的域名解析

完成 COS 配置后,需要将博客域名解析到腾讯提供CDN节点上的地址。

添加持续集成自动发布到COS(Travis CI)

为了发布到 COS,站点的 _config.yml 会添加下面的代码。

1
2
3
4
5
6
7
 deploy:
- type: cos
secretId: XXX_ID
secretKey: XXX_KEY
appId: 1252086360
bucket: blog-1252086360
region: ap-shanghai

其中出现的 secretId 以及 secretKey 是私钥,不要在公开仓库展示。通过Travis-ci 中添加 Environment Variables 解决。

很多教程里,他们的 _config.yml 不会出现 secretId 和 secretKey 这两行,取而代之的是让你在 .travis.yml 添加几行。

1
2
3
4
5
6
script 
- hexo d
env:
global:
- secretId: ${secretId} # Environment Variables 中配置
- secretKey: ${secretKey} # Environment Variables 中配置

按照这样设置,build 时,出现错误提示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{ error:
{ Code: 'InvalidAccessKeyId',
Message: 'The access key Id format you provided is invalid.',
Resource:
'blog-1252086360.cos.ap-shanghai.myqcloud.com/2012/01/23/2011/index.html',
RequestId: 'NWNlMDU3NzlfNWI5ZDA4MDlfNWVlMF81ZWUzNTg=',
TraceId:
'OGVmYzZiMmQzYjA2OWNhODk0NTRkMTBiOWVmMDAxODc0OWRkZjk0ZDM1NmI1M2E2MTRlY2MzZDhmNmI5MWI1OTQyYWVlY2QwZTk2MDVmZDQ3MmI2Y2I4ZmI5ZmM4ODFjMDU3YThkNThjZmQ1NWVkMGY2ZDBiNGM1YTEyNGIzMGM=' },
statusCode: 403,
headers:
{ 'content-type': 'application/xml',
'content-length': '513',
connection: 'keep-alive',
date: 'Sat, 18 May 2019 19:05:29 GMT',
server: 'tencent-cos',
'x-cos-request-id': 'NWNlMDU3NzlfNWI5ZDA4MDlfNWVlMF81ZWUzNTg=',
'x-cos-trace-id':
'OGVmYzZiMmQzYjA2OWNhODk0NTRkMTBiOWVmMDAxODc0OWRkZjk0ZDM1NmI1M2E2MTRlY2MzZDhmNmI5MWI1OTQyYWVlY2QwZTk2MDVmZDQ3MmI2Y2I4ZmI5ZmM4ODFjMDU3YThkNThjZmQ1NWVkMGY2ZDBiNGM1YTEyNGIzMGM=' } }
FATAL Something's wrong. Maybe you can find the solution here: http://hexo.io/docs/troubleshooting.html
TypeError: Cannot read property 'statusCode' of undefined
at uploadFileToCOS.catch.then.data (/home/travis/build/xiang578/xiang578.github.io/node_modules/hexo-deployer-cos/lib/deployer.js:42:16)
at process._tickCallback (internal/process/next_tick.js:68:7)

出现这个问题是 hexo -d 时,_config.yml 无法获得环境变量 secretId 和 secretKey 的。会导致没有秘钥。

参考 使用 Travis CI 部署你的 Hexo 博客 - 知乎 ,在 .trvis.yml 文件的 hexo d 命令前,加入下面两行即可解决。

1
2
- sed -i "s~XXX_ID~${secretId}~" _config.yml
- sed -i "s~XXX_KEY~${secretKey}~" _config.yml

之后build 时,会自动利用环境变量中 secretId 和 secretKey 的值替换 _config.yml 文件缺省的值。

最后提供我的两份配置文件给大家参考:_config.yml.travis.yml

3. 百度收录

之前,我一直将博客的静态文件存储在 github 的项目中,也使用插件生成 baidusitemap 文件。但是由于一些不为人知的秘密,百度的爬虫实际上无法爬取 github 上的资源,导致博客最新的文章没有被收录到百度中。

而且从百度提供的抓取诊断上来看,配置腾讯云 COS 后,百度的爬虫依然访问的是 github 上的仓库。

-w558

一顿搜索之后,找到一个主动提交 hexo 博客链接至百度的插件 huiwang/hexo-baidu-url-submit

参考 Hexo插件之百度主动提交链接 | 王辉的博客 以及 Hexo百度主动提交链接 - 简书 完成配置。

  • 安装插件 cnpm install hexo-baidu-url-submit --save
  • 修改根目录下面的 config.yml 文件,配置 baidu_url_submit 和 deploy。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
baidu_url_submit:
count: 100 ## 比如3,代表提交最新的三个链接
host: xiang578.com ## 在百度站长平台中注册的域名
token: your_token ## 请注意这是您的秘钥, 请不要发布在公众仓库里!
path: baidu_urls.txt ## 文本文档的地址, 新链接会保存在此文本文档里

deploy:
- type: cos
secretId: XXX_ID
secretKey: XXX_KEY
appId: 1252086360
bucket: blog-1252086360
region: ap-shanghai
- type: baidu_url_submitter

上面的代码中出现一个 token,由于这是一个私有的,不能出现在 github 公开的仓库中。所以也需要 Travis-ci 中添加 Environment Variables 解决。和前文提到相同,在 .travis.yml 中添加 - sed -i "s~your_token~${BD_TOKEN}~" _config.yml 解决私钥问题。

最终在 travis-ci 中发现下面的日志即配置成功。另外一点,百度的站长平台的数据不能及时展示我们提交后的结果,需要耐心等待。

-w866


ImageNet Classification with Deep Convolutional Neural Networks(AlexNet)

作者以及相关性

  • Alex Krizhevsky
  • Ilya Sutskever
  • Geoffrey E. Hinton
  • 本文被认为是这一轮深度学习浪潮的开端

主题

  • 将 CNN 技术最先应用到图像识别领域,利用 CNN 参数共享的特性,减少网络的规模
  • 解决深度网络难训练(速度慢)以及容易过拟合问题(更多数据或者网络技巧)

数据集与指标

  • ImageNet LSVRC-2010 contest: 图片 1000 分类
  • top-1 和 top-5 错误率为指标

模型/实验/结论

模型

  • 由于当时的 GPU 显存限制,无法将所有的数据加载到单独 GPU 中,作者使用两个 GPU 并行训练。

  • 整个模型如下图所示,由 5 个卷积层以及 3 个全连接层组成。其中在 CONV3、FC1、FC2、FC3 层进行两个 GPU 的数据交互。

    • [227, 227, 3] INPUT: 原始论文中 224 为笔误。
    • [55, 55, 96] CONV1: (11*11*3,96) filters, Stride 4, pad 0
    • [27, 27, 96] MAX POOL1: (3*3) filters, Stride 2
    • [27, 27, 96] NORM1: Normalization layer
    • [27, 27, 256] CONV2: (5*5,256) filters, Stride 1, pad 2
    • [13, 13, 256] MAX POOL2: (3*3) filters, Stride 2
    • [13, 13, 256] NORM2: Normalization layer
    • [13, 13, 384] CONV3: (3*3,384) filters, Stride 1, pad 1
    • [13, 13, 384] CONV4: (3*3,384) filters, Stride 1, pad 1
    • [13, 13, 384] CONV5: (3*3,256) filters, Stride 1, pad 1
    • [6, 6, 256] MAX POOL3: (3*3) filters, Stride 2
    • [4096] FC1: 两个 GPU 中的 CONV 层结果进行全连接
    • [4096] FC2: FC1 进行全连接
    • [1000] FC3: FC2 进行全连接,最后输出分类结果
  • 参数数量 60 million

  • 使用 ReLU 作为激活函数:比 tanh 计算开销小,以及收敛速度快。根据问题的特点选择激活函数(大模型、大数据集)
  • Local Response Normalization(Norm Layers):局部响应归一化层,后来很少使用。
    在经过 ReLU 作用之后,对相同空间位置上(bx,y{b_{x,y}})的相邻深度(bj{b^j} )的卷积结果做归一化。n 指定相邻卷积核数目,N 为该层所有卷积的数目。k,n,α,β{k, n, \alpha, \beta} 都是超参数。本文使用 k=2,n=5,α=104,β=0.75{k=2, n=5, \alpha=10^{-4}, \beta = 0.75}, 分别降低 top-1 和 top-5 错误 1.4% 和 1.2%

bx,yi=ax,yi/(k+αj=max(0,in/2)min(N1,i+n/2)(ax,yj)2)βb_{x, y}^{i}=a_{x, y}^{i} /\left(k+\alpha \sum_{j=\max (0, i-n / 2)}^{\min (N-1, i+n / 2)}\left(a_{x, y}^{j}\right)^{2}\right)^{\beta}

  • Pooling:s=2 < z=3,有部分重叠,作者通过实验发现这种方法可以更好地避免过拟合。
  • data augmentation:
    • 对图像进行裁剪以及翻转,扩大数据。这种策略对测试带来影响,测试时裁剪出图片四个角落以及中间部分,得到 5 张图片,另外翻转得到 5 张图片,最后分类结果又这 10 图片的平均得分确定。
    • 利用 PCA 改变 RGB 通道的强度。
  • Dropout:每次训练的时候,从模型中 sample 出一个小的模型,减少过拟合。

实验

  • 参数:dropout 0.5,batch size 128, SGD Momentum 0.9, Learning rate 1e-2 reduce by 10,L2 weight decay 5e-4

  • 测试集上结果

  • 取出 CONV1 相关的 filters卷积侧重点不同,GPU1 颜色无关,GPU2 颜色相关。多次实验发现都存在这种现象,说明使用多个 GPU 训练是必要的,模型可以捕捉更多信息。

  • 取所有最后一个隐层向量,找到与测试图片欧拉距离最小的训练图片(下图中第一列为测试图片,之后几列是欧拉距离最小的训练集中图片)。肉眼可以发现,同一分类的图片有很大关联性。证明模型能学习图片之间的关系。

结论

  • 通过移除 AlexNet 网络中的某几层发现错误率均有提高,这个网络时必要以及有效的。
  • 文章中作者通过大量的实验确定模型的细节问题,值得我们学习。
  • 当时的 GPU 限制作者的想象力……

2018 探索

全文混乱。拖了 4 个月之后,强行完结。

毕业

2018 最大的一件是自己终于艰难地从学校毕业。本来在学校属于 easy 模式,原本以为毕业很轻松。不过出于一些原因,比其他人多待一个月才拿到毕业证书,给我不太美好的大学生涯又多添几份痛苦。本来还准备写篇文章来总结一下大学生涯,拖到现在毕业都快一周年,也只能当成是毕业一周年的回忆文章。

工作

毕业之后,用我外婆说的一句话“一个人拉着两个行李箱就去工作了”。误打误撞和机器学习挂上一些关系。每天属于虽然工作很开心,但是好像没有干什么事情的状态。更多地吐槽也准备写在工作一周年的文章中。

自我管理

这个概念是年初感觉自己太混乱时提出来的,如果成为更好的自己。一年来有过很多想法和实践,但是现在还探索出来完整的系统。有机会再写。

年度阅读

说来惭愧,今年没有读多少本书,而且绝大部分都是在没有毕业时候读的。工作之后,完整看完地也只有一本《九败一胜》。这本书讲的是王兴的创业故事,总的感受是创业维艰。感觉王兴是为了创业而生的人,有知识基础,又有经济基础。在多次创业之后,培养了商业上的灵敏,管理上的艺术。最终能在千团大战中走出来,成就今天的美团帝国。可惜这个冬天,美团有些艰难,脉脉上给予他裁团(裁员,特别是应届生)、C团(绩效打 C,逼你走)的名声。比起王兴的故事,我更感兴趣的是程维创立滴滴的故事,不知道什么时候可以读到。

说回来在读过的书中,最推荐 软技能,之前也写过简单的介绍。用时髦的话来说,这本书教你成为一个斜杠青年。在基础的工资外,还有通过其他渠道有第二职业的收入,最后是睡后收入(表名上说的是睡觉时候获得的收入,第二层含义是一次生产,可以多次贩卖)。后来想想,自己可以二次出售什么?无法是什么时间管理、知识管理、理财、读书、写作之类的烂大街的东西。所以,自己还是需要加强抗击职业风险的能力,尽快找到自己的第二职业收入。

另外,自己也进行了一些主题阅读。年初的时候,对时间管理和知识管理感兴趣。读过Evernote 100个做笔记的好方法Evernote超效率数字笔记术印象笔记留给你的空间有道云笔记:记录,成为更好的自己你的知识需要管理,看完这些书多少有些收获,但也没有完全解答我的疑问,说回来,也不太推荐你们去看。不过,时间管理方面的两本书,小强升职记搞定Ⅰ,却是五星推荐,看一看,多少能提高一些工作效率。

年度观影

今年看过的电影倒是比书多一些。不过,其中好多都是漫威的超级英雄片。自己感觉漫威伟大的地方在于创造了一个包括神话、物理、外星文明的电影宇宙,这个宇宙也许会成为我们这一代人的回忆。

说回来,今年看过的片子中,最推荐的是无问西东。这部片子讲述了不同时期 4 个不同年代清华学子关于选择的故事,也许是因为没有他们这样的大学经历才会嫉妒。看完片子后,还抄录一些台词,大概能更加清晰的表达电影对我的影响。

吴岭澜(文科很好,理科很差)面对梅校长时候询问为什么不去读文科时的回答。

因为最好的学生都读实科
我只知道,不管我将来做什么
在这个年纪,读书,学习都是对的
我何用管我学什么?
每天把自己交给书本,就有种踏实

吴岭澜重新找到自己的目标之后,成为了清华大学的一名教授。在西南联大给学生上课时回忆自己的大学时光:

当我在你们这个年纪,有段时间,我远离人群,独自思索,我的人生到底应该怎样度过?某日,我偶然去图书馆,听到泰戈尔的演讲,而陪同在泰戈尔身边的人,是当时最卓越的一群人,这些人站在那里,自信而笃定,那种从容让我十分羡慕。而泰戈尔,正在讲“对自己的真实”有多么重要,那一刻,我从思索生命意义的羞耻感中,释放出来。原来这些卓越的人物,也认为花时间思考这些,谈论这些,是重要的。今天,我把泰戈尔的诗介绍给你们,希望你们在今后的岁月里,不要放弃对生命的思索,对自己的真实。

对吴岭澜的总结:

梅校长说:“人把自己置身于忙碌当中,有一种麻木的踏实,但丧失了真实,你的青春也不过只有这些日子。”
什么是真实?
你看到什么,听到什么,做什么,和谁在一起
有一种,从心灵深处,满溢出来的不懊悔,也不羞耻的平和与喜悦

后来吴岭澜领悟到了:
看到和听到的,经常令你们沮丧,世俗是这样强大,强大到生不出改变它们的念头。可是如果有机会提前了解了你们的人生,知道青春也不过只有这些日子,不知你们是否还会在意的,那些世俗让你们在意的事情,比如占有多少,才更荣耀,拥有什么,才能被爱。 等你们长大,你们因绿芽冒出土地而喜悦,会对出生的朝阳欢呼雀跃,也会给别人善意和温暖,但是却会在赞美别的生命的同时,常常,甚至永远忘了自己的珍贵。愿你在被打击的时,记起你的珍贵,抵抗恶意;愿你在迷茫时,坚信你的珍贵,爱你所爱,行你所行,听从你心,无问西东。

富家子弟沈光耀放弃学业,决定参加飞行队时,母亲不远万里来联大劝他。

“当初你离家千里,来到这个地方读书,你父亲和我都没有反对过,因为,是我们想你,能享受到人生的乐趣,比如读万卷书行万里路,比如同你喜欢的女孩子结婚生子。注意不是给我增添子孙,而是你自己,能够享受为人父母的乐趣,你一生所要追求的功名利禄,没有什么是你的祖上没经历过的,那些只不过是人生的幻光。我怕,你还没想好怎么过这一生,你的命就没了啊!”

同学在他牺牲后,去看望沈母时,屏幕上展现出一幅对联:三代五将护国定疆青史留正气,六韬三略擅用筹边御旨赞英豪。

这部电影的彩蛋标题是致敬时代的风骨,快速回顾在电影中出现过的时代名人。可惜自己没有认出多少个,真是悲哀。

后记

这篇文章写的有点杂,我只是看着 MWeb 中的存稿有点多,趁着这次机会消灭一些,来年有机会写些新的东西。

于北京回龙观

其他文章: