1 | # coding=utf-8 |
Shell后台运行
- 使用
nohup
和&
来执行some_cmd
1 | nohup some_cmd >out 2>&1 & |
原理:
后台执行的进程,其父进程还是当前终端shell的进程,而一旦父进程退出,则会发送hangup信号给所有子进程,子进程收到hangup以后也会退出。
如果我们要在退出shell的时候继续运行进程,则需要使用nohup忽略hangup信号(或者setsid将将父进程设为init进程,进程号为1)。
脚本中示例:1
2
3
4
5
6
7
8
9
10
11 !/bin/bash
run two processes in the background and wait for them to finish
nohup sleep 3 &
nohup sleep 10 &
echo "This will wait until both are done"
date
wait
date
echo "Done"
注意,可以使用wait等待脚本当前产生所有子进程结束。
- 对已经跑起来的some_cmd
1 | some_cmd |
但这样问题是,终端(意外)退出,该进程会被关闭。
- 可以使用
disown
1 | some_cmd |
- 还有一个关于subshell的小技巧
我们知道,将一个或多个命名包含在()
中就能让这些命令在子shell中运行。
当我们将&
也放入()
内之后,新提交的进程的父ID(PPID)为1(init 进程的 PID),并不是当前终端的进程 ID。
因此并不属于当前终端的子进程,从而也就不会受到当前终端的hangup信号的影响了。
PyTorch Cheatsheet
Tensor
1 | import torch |
Tensor与numpy
1 | # 此处演示tensor和numpy数据结构的相互转换 |
Autograd
1 | from torch.autograd import Variable |
Neural Network
- 定义一个有着可学习的参数(或者权重)的神经网络
- 对着一个输入的数据集进行迭代:
- 用神经网络对输入进行处理
- 计算代价值 (对输出值的修正到底有多少)
- 将梯度传播回神经网络的参数中
- 更新网络中的权重
- 通常使用简单的更新规则: weight = weight + learning_rate * gradient
定义网络1
2
3
4
5
6
7
8
9
10
11
12import torch.nn as nn
import torch.nn.functional as F
class LRModel(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
# linear内置了参数初始化方法,此处无需显式指定
self.linear = torch.nn.Linear(2, 1) # 2 in, 1 out
def forward(self, x):
y_pred = F.sigmoid(self.linear(x))
return y_pred
仅仅需要定义一个forward函数就可以了,backward会自动地生成。
你可以在forward函数中使用所有的Tensor中的操作。
模型中可学习的参数会由net.parameters()返回。
定义loss与optimizer1
2criterion = torch.nn.BCELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
训练迭代1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17x_data = Variable(torch.Tensor(X))
y_data = Variable(torch.Tensor(T))
model = LRModel()
for epoch in range(1000):
# Forward pass: Compute predicted y by passing x to the model
y_pred = model(x_data)
# Compute and print loss
loss = criterion(y_pred, y_data)
print(epoch, loss.data[0])
# Zero gradients, perform a backward pass, and update the weights.
optimizer.zero_grad()
loss.backward()
optimizer.step()
Deep CXR
0. Naive Method
- categorical fields -> one-hot encoding
- 上面直接堆叠deep,deep一般是MLP,multi-layer perceptron
1. 对高维稀疏特征进行embedding
one-hotted features高维稀疏,这对LR可以勉强忍受,但对DNN计算开销太大,拖低了模型性能effectiveness & efficiency。
对此,将各个field进行embedding,即将一个高维稀疏向量(one-hot),映射为一个低维稠密向量(real values)。
Deep Crossing
- Microsoft 2016
- embedding:通过dense,将各个稀疏域转化为低维稠密特征向量并concat在一起
- deep:5层残差网络(何恺明ResNet),避免梯度爆炸、消失
2. 交叉特征与低维特征
DNN的优势在于能够自动学得高抽象层次(high-level)的特征,这些特征在而CV、NLP中往往是非常有用的,因为CV、NLP往往解决的是human替代任务,即模型代替人工进行工作。
但CXR预估并不是一个human替代任务,因而除了DNN能够隐式学得的高抽象层次特征,还应该尝试向模型中引入一些其他的特征来提升模型性能。
引入点包括:1)交叉特征;2)低抽象层次特征。
对于交叉特征,由于DNN的节点计算方式(对输入加权求和,再套一个非线性,不存在特征间相乘),对feature interactions并不能很好的描述。
为此,可以主动做特征交叉,补充到模型中,以增加模型对于特征间interactions的表达能力。
模型结构调整方式:生成的交叉特征,可以作为deep的input的补充,输入到deep中(串行结构);也可以concat在deep旁边,与deep结果一起进入最终预测节点(并行结构)。
对于低抽象层次特征,由于我们的CXR预估任务并不是只关注于高抽象层次,故模型不仅使用DNN的高抽象层次特征对应结果,还应该考虑引入低抽象层次特征,综合两种层次的特征来生成结果。
模型结构调整方式:低抽象层次特征,concat在deep的低抽象层次特征旁边,与一起进入最终预测节点(并行结构)。
2.1. 引入FM对特征做embedding [描述feature interactions]
- FM的特异之处:
- FM基于隐向量,可以学得数据集中出现次数较少、或根本未出现的特征组合
- 使用FM进行embedding,尤其对于高维稀疏的one-hotted features,能通过2阶交叉特征的引入,增强模型的预测能力
- 当然,FM的shortcomings也很明显:1)linear model;2)at most 2-order features
FNN
- 2016
- pretrain一个基于Dense的FM,做embedding层,上面叠加deep
- embedding层包含FM一阶、二阶部分(将u_units看做,即1个一阶参数,u_units - 1个二阶参数)
2.2. 在embedding和deep之间插入“特征交互描述”层 [描述feature interactions]
我们知道基于dense做embedding、然后进行concat、然后上面堆叠deep,并不能描述feature interactions。
对此,在embedding和deep之间插入“特征交互描述”层,来专门描述feature interactions。
PNN
- 2016
- 在embedding和deep之间加入了product layer进行不同field之间特征交叉
- 使用inner product、outer product
- inner product相当于引入了FM的内积部分
- outer product会得到一个矩阵,而非像inner product得到一个数。向后进入deep在一个unit中加权求和时,与一个数操作类似,只不过是对矩阵各个元素加权求和
- product得到的结果(一系列数值),同时concat一阶特征,作为deep输入
- 不是被动通过deep学习特征之间关系,而是强制通过product层引入field之间特征交叉
NFM
- 2017
- NFM中使用:
- ,即文中所谓Bi-Interaction,本质上是一个pooling,对得到的embeddings进行了encode而非concat:
- 1)embedding层,隐向量feat_val(因为不只one-hot输入,还有real-value输入),获取embeddings;
- 2),即“各种组合的元素乘”的向量和,此结果shape与的shape相同,都是embedding_size
- 这个结果encode了embedding space的2-order feature interactions,作为“将feature embeddings进行concat”的替代
- 3)上面再堆叠deep
- 相较于FNN,NFM的网络结构中多了一个Bi-Interaction - sum pooling,形式上更像FM(但element-wise product后未将向量各元素累加,仍保留向量形式,所以还不是内积)
- 相较于PNN,PNN也是不想直接将embeddings concat后输入到deep中,觉得会欠缺feature interactions,对此PNN的做法是各两个embeddings内积得到一个数之后,再将各个数concat在一起,再concat上一阶特征,输入到DNN中;而NFM则不是对各两个embeddings做内积,而是做元素乘,然后sum pooling
- 相较于Wide & Deep(或DeepFM),NFM不通过joint一个Wide来引入cross features,而是在embedding与deep之间加一个Bi-Interaction pooling来描述特征交叉(in the low level)
- NFM更像FNN、PNN,但也可看成是下文Wide & Deep的一种改进形式(因为一阶特征未进入deep,而是进入了最终结果层)
AFM
- 2017
- AFM是在NFM基础上改的,作者中包含NFM的作者
- 加入了attention机制,即,Pire-wise Interaction之后,根据组合不同,后续操作时分配不同的权重
- 即在Interaction Layer中,sum pooling时,为每个“组合的元素乘”分配一个权重,再做向量和。(此结果shape与的shape相同)
- 这个attention weight是不好学习的:数量级、有可能组合未出现过。对此参数的学习,单独使用一个所谓的attention network
- attention network输入为各种“组合的元素乘,输出维度为各组合数量的softmax。这样对于某个,我们将该网络的结果项作为attention weight
- Interaction Layer之后,原文的AFM没有再堆叠deep了
2.3. 为deep joint一个wide结构 [同时考虑低抽象层次特征]
Wide & Deep
- Google 2016
- Wide部分:直接对原始特征、交叉特征(人工选择)做linear
- Deep部分:先用Dense做embedding,然后上面叠加deep
- Deep部分的参数使用随机初始化,未pretrain
- joint train Wide部分和Deep部分,即,两部分做linear后,套一个sigmoid,作为结果,使用一个loss做优化
- Wide部分本身就是linear,所以两部分做linear时不需要再乘一个权重,只给Deep部分结果乘一个权重即可
2.3.1. 在wide中考虑特征交叉 [同时考虑低抽象层次特征、描述feature interactions]
DeepFM
- Huawei 2017
- 对Wide & Deep引入FM进行优化
- 最底层使用Dense
- Wide部分:使用FM(包含一阶、二阶、常数项,Dense作为FM的二阶部分)的结果,作为低抽象层次特征
- Deep部分:使用Dense(FM的二阶部分)作为embedding层
- Dense、FM的参数使用随机初始化,未pretrain(若做pretrain,label其实与overall network的label相同,所以无需做pretrain,直接end-to-end训练)
DCN
- Google 2017
- Wide部分 -> Cross部分:N层可显式的表示到N阶的特征交叉
- 每层为一个变换节点,为
- 是个矩阵,使得交叉特征阶数+1,则保存了之前所有低阶交叉特征
- 最后层的输出,相当于各阶交叉特征加权求和
xDeepFM
- Microsoft 2018
- 对比其他
- Wide & Deep的deep结构中,将隐向量的各个bit进行交互。这是有一定问题的:这种交互是bit-wise描述的,它意识不到field隐向量的概念。并且在multi-hot的field中,同一隐向量内各个bit也会各自影响。这些与FM的初衷是不一致的。
- DeepFM的wide结构中,倒是引入了vector-wise的交互描述。但它只有2阶。
- DCN在wide结构中,同样引入了vector-wise的交互描述,并且阶数可由网络层数指定。但其只能学习某特定形式的高阶交互,并且也是bit-wise描述的。
- xDeepFM的wide结构中,cross方式(Compressed Interaction Network,CIN):
- 令表示第层的输出,其中表示第层的vector个数(n_units),vecor维度始终为,保持和输入层一致(vector-wise操作)。具体地,第层每个vector的计算方式为:
- 其中表示第层的第个vector(或者理解成DNN一层中的一个unit)的权重矩阵,表示元素乘
- 其实就是:取前一层中的个vector,与输入层中的个vector,进行两两元素乘运算,得到个vector。然后个unit有个权重矩阵,每个unit中的个vector,乘以相应的权重矩阵后相加(加权求和)。这样,每个unit输出1个维度为的vector,本层输出为
- 这样,在第层,CIN只包含阶的组合特征。最终,需要CIN将每层的中间结果都输出出来
- sum pooling:,即将第层中,个units中,各vector的bit进行累加。注意,这隐含着内积的思想。最终,对于该层,会得到个数,作为一个vector
- 最终,将各层concat在一起,作为CIN输出,(并且concat上raw,作为wide输出)
- concat上deep(MLP)的结果,作为最终预测层输入
3. 加入attention机制
DIN
- Ali 2018
- 对于“用户的历史商品列表”这个multi-hot特征,在embedding后得到不定个数的embeddings向量。通常做法是将这项向量sum pooling或avg pooling,然后concat到其他特征embeddings,输入给deep。进一步,在做sum pooling时引入weights,表现不同的历史兴趣item,重要性不同
- 在遇见sum pooling、avg pooling这种东西时,都可以考虑进一步引入weights
- 如果只是加一个general weights,其描述的“不同item的重要程度”是固定的,不会区分场景。再进一步,对于不同candidate ad,可以让weights不同,即,对于不同的ad,商品embeddings的weights是不同的,然后再weighted sum pooling,获取基于历史信息的、用户对该商品的兴趣表达,即引入了attention机制
- 理解一下,attention不仅仅是加weights,还需要对于不同的query,使用的是一组不同的weights(一个query对应一组weights)
- recall一下AFM,其实是在其设计的NFM的interaction layer中出现了sum pooling,就向其中进一步引入了weights,从而声称使用了attention机制(但其实并没有query的概念,也就没有为每个query生成不同的weights组的概念,仅是一个general的weightes进行sum pooling)
- attention weights的计算:local activation unit
- 某item embedding(待weighted项)与ad embedding(query)进行element-wise product,然后concat两个embeddings,输入一层deep(输出未进行softmax,想描述weights的绝对差异)(u_units对齐embedding_size,保证weight能element-wise product上去)(end-to-end training)
DIEN
- Ali 2018
- 本深度模型关注点,不同域之间的特征交互描述 -> 用户兴趣表示
- 对于“行为列表”这类特征,考虑时序,使用seq模型
- 不同于DIN中,用户行为列表是没有顺序的,故使用multi-hot
- 对用户interest的representation,往往直接使用用户行为(embedding & pooling);DIEN尝试通过显示的用户行为,提取隐含的用户当前兴趣表示、及兴趣发展趋势
- 时序兴趣特征提取:
- GRU,使用下一动作作为监督信号
- 描述特征之间的影响
- 兴趣发展进程:
- Attention Update GRU
- 描述与target AD相关的interest
[DSIN]
- Ali 2019
- 以session为粒度
TODO
xDeepFM
FiBiNet
Behavior Sequence Transformer
Deep Spatio-Temporal Neural Networks for Click-Through Rate Prediction
ATRank: An Attention-Based User Behavior Modeling Framework for Recommendation
AutoInt
CNN系CCPM、FGCNN
4. 多任务学习
通常来说,如果你发现你需要的不止一个损失函数,你就是在做多任务学习。
- MTL可以有效增加用于模型训练的样本量
- 学到更好的特征表示、关系
- 约束参数适应于各个子任务,降低过拟合风险
- 其实感觉实际上更多是:1)为了获取更通用的特征表达,对各个任务都有利;2)单独某个任务比较困难,可能是样本不够,需要其他的任务带一带。
4.1. 解决“训练数据空间”与“预测数据空间”不一致的问题
ESMM
- Ali 2018
- pCVR的问题:Sample Selection Bias,即pCVR训练基于点击数据,推理却基于曝光数据
- 现在想对pCVR使用全特征域(曝光数据)学习
- 分析:
- 其中,与是可以通过全特征域学习的
- 故,可以引入多任务学习MTL,使用全域特征显示学习与,同时隐式学习
- 模型关键:
- 一个网络、一个网络(网络结构为简单的embedding+MLP),结果相乘得到
- loss精巧设计,显示的、joint训练与,这两部分都可以通过全域特征进行训练,一定程度的消除了Sample Selection Bias
- loss由两部分组成,即显示学习、joint训练的pCTR部分和pCTCVR部分。而pCVR只能隐式学习,因为1)loss中各任务应该是基于全域特征学习的;2)其负样本标注是有些问题的。在loss中,要保证label都是准的。所以,pCVR不适合基于全域特征,在loss中直接进行监督
- pCVR使用全域特征,隐含了一个负样本标注的问题:“CVR预估到底要预估什么”,论文虽未明确提及,但理解这个问题才能真正理解CVR预估困境的本质。想象一个场景,一个item,由于某些原因,例如在feeds中的展示头图很丑,它被某个user点击的概率很低,但这个item内容本身完美符合这个user的偏好,若user点击进去,那么此item被user转化的概率极高。CVR预估模型,预估的正是这个转化概率,它与CTR没有绝对的关系,很多人有一个先入为主的认知,即若user对某item的点击概率很低,则user对这个item的转化概率也肯定低,这是不成立的。更准确的说,CVR预估模型的本质,不是预测“item被点击,然后被转化”的概率(CTCVR),而是“假设item被点击,那么它被转化”的概率(CVR)。这就是不能直接使用全部样本训练CVR模型的原因,因为咱们压根不知道这个信息:那些unclicked的item,假设他们被user点击了,它们是否会被转化。如果直接使用0作为它们的label,会很大程度上误导CVR模型的学习。
- 注意,loss虽然是对pCTR部分和pCTCVR部分的描述,但其参数还是来自于pCTR网络和pCVR网络的,即
- 是点击label,是转化label,是单独一个网络的loss函数(交叉熵),是模型函数
- 此外,两个网络共享embedding,joint学习,这也解决了转化正样本稀疏的问题,Data Sparsity
- 当然,pCVR网络的训练、推理,都使用全域特征了哦。即,隐式学得了一个输入全域特征,预测的子网络
- 为什么使用乘法形式,而不使用除法形式,即?
- 因为数值往往较小,容易造成数值溢出
ESM2
DUPN
Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts
CTR预估中负采样修正
负采样
CTR预估中,负类(label==0)的样本数往往远大于正类。
为了解决样本不均匀的问题,往往对负类进行采样,就是所谓的负采样。
但需要注意的是,负采样后的结果,会使得测试集中CTR被高估:
- 相当于将训练集中一部分真实的负例排除掉了,使得模型获取的信息不足,并不能准确地识别出(这些)负例,从而在测试集中将一部分负类向正类预测,即CTR被(整体)高估。
这在CTR预估中是有问题的,涉及到“保序”与“保距”的概念:
- 假设我们有这么一个序列“牛 500KG,羊100KG,兔子 5kg”,我们有一个模型,输入这些动物之后,根据体重排序,并且出一个体重的预估值。如果模型只是采用AUC这个指标的话,那么我们模型输出“牛 100kg,羊 20 kg,兔子1kg”,这样的结果是没问题的,但是这只是做到了保序,但是他们之间的差值变小了,没有做到保距。
- 在CTR预估中,比如我们有这么一些广告,A的实际点击率是10%,B的实际点击率是5%,C的实际点击率是1%,但是A B C的点击收益分别是2,5,10,如果我们的模型没有做到保距,那么输出的预估值是5%,1%,0.5%,这样的话AUC的排序指标是满足了,但是实际收益并不是最优的。
因此需要对负采样后的CTR预估值进行校准,使得整体CTR距离真实值越近越好。
修正方法
$p$为负采样后预测值,$w$为采样率$[0,1]$,$q$为修正后的预测概率。
推导:
假设输入向量$x$,预测标签$C_k$,那么可以用条件概率表示,即计算$p(C_k|x)$的概率。根据贝叶斯公式,条件概率:
上面是没有做重采样时,得到概率。
当做重采样时,只是改变了标签$C_k$的先验概率$p(C_k)$,即将$p(C_k)$变为$p′(C_k)$($C_k$的先验分布改变)。而$p(x)$是条件$x$发生的概率,不会变化。$p(x|C_k)$是后验概率,也不会变化。(假设均匀采样,不改变样本中正样本分布)
对负样本进行抽样,比例为$w$,则$n’(0)=n(0)\times w$,$n’(1)=n(1)$,则先验概率$p(1)$与$p′(1)$的关系为:
由此,若想修正$p(C_k|x)$,则:
由于预测时并不能获知正样本比例$p’(1)$,则令$p’(1) \approx p’(1|x)$
群体比例由单体概率估算