理论知识 搜索推荐理论 V1

“对部分机器学习相关理论进行整理"

Posted by SunQH on August 29, 2021

推荐系统理论

🚩GBDT+LR

https://zhuanlan.zhihu.com/p/37522339

使用GBDT的好处:利用GBDT可以自动进行特征筛选和特征组合,进而生成新的离散特征向量。因为回归树中每个节点的分裂是一个自然的特征选择的过程,而多层节点的结构则对特征进行了有效地自动组合。所以可以非常高效地解决棘手的特征选择和特征组合的问题。

实验中设置30棵树,深度为8。每颗树都相当于一个类别特征,每棵树的叶子结点数相当于特征值的个数。

  1. 首先将数值型的连续特征作为lgb的输入,(假设100棵树,设置的叶子节点数num_leaves=64)然后进行特征转换。
  • 训练GBDT模型 本文使用lightgbm包来训练我们的GBDT模型,训练共100棵树,每棵树有64个叶子结点。
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
df_train = pd.read_csv('data/train.csv')
df_test = pd.read_csv('data/test.csv')

NUMERIC_COLS = [
    "ps_reg_01", "ps_reg_02", "ps_reg_03",
    "ps_car_12", "ps_car_13", "ps_car_14", "ps_car_15",
]

print(df_test.head(10))

y_train = df_train['target']  # training label
y_test = df_test['target']  # testing label
X_train = df_train[NUMERIC_COLS]  # training dataset
X_test = df_test[NUMERIC_COLS]  # testing dataset

# create dataset for lightgbm
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

params = {
    'task': 'train',
    'boosting_type': 'gbdt',
    'objective': 'binary',
    'metric': {'binary_logloss'},
    'num_leaves': 64,
    'num_trees': 100,
    'learning_rate': 0.01,
    'feature_fraction': 0.9,
    'bagging_fraction': 0.8,
    'bagging_freq': 5,
    'verbose': 0
}

# number of leaves,will be used in feature transformation
num_leaf = 64

print('Start training...')
# train
gbm = lgb.train(params,
                lgb_train,
                num_boost_round=100,
                valid_sets=lgb_train)

print('Save model...')
# save model to file
gbm.save_model('model.txt')

print('Start predicting...')
# predict and get data on leaves, training data
  • 特征转换

在训练得到100棵树之后,我们需要得到的不是GBDT的预测结果,而是每一条训练数据落在了每棵树的哪个叶子结点上,因此需要使用下面的语句:

1
y_pred = gbm.predict(X_train, pred_leaf=True)

打印上面结果的输出,可以看到shape是(8001,100),即训练数据量*树的棵树

1
2
print(np.array(y_pred).shape)
print(y_pred[0]) # 看第一条样本的输出,包含100颗数的输出节点,即每个样本在不同类别特征上的取值。

结果为:

1
2
3
4
5
6
(8001, 100)
[[43 26 47 47 47 19 36 19 50 52 29  0  0  0 46 23 13 27 27 13 10 22  0 10
   4 57 17 55 54 57 59 42 22 22 22 13  8  5 27  5 58 23 58 14 16 16 10 32
  60 32  4  4  4  4  4 46 57 48 57 34 54  6 35  6  4 55 13 23 15 51 40  0
  47 40 10 29 24 24 31 24 55  3 41  3 22 57  6  0  6  6 57 55 57 16 12 18
  30 15 17 30]]

其实实验中到这一步就可以了,得到了每个样本落到每颗树的叶子节点位置,相当于这样样本在每个类别特征中的取值。


  • 然后我们需要将每棵树的特征进行one-hot处理,如前面所说,假设第一棵树落在43号叶子结点上,那我们需要建立一个64维的向量,除43维之外全部都是0。因此用于LR训练的特征维数共num_trees * num_leaves。
1
2
3
4
5
6
print('Writing transformed training data')
transformed_training_matrix = np.zeros([len(y_pred), len(y_pred[0]) * num_leaf],
                                       dtype=np.int64)  # N * num_tress * num_leafs
for i in range(0, len(y_pred)):
    temp = np.arange(len(y_pred[0])) * num_leaf + np.array(y_pred[I])
    transformed_training_matrix[i][temp] += 1
  • 当然,对于测试集也要进行同样的处理:
y_pred = gbm.predict(X_test, pred_leaf=True)
print('Writing transformed testing data')
transformed_testing_matrix = np.zeros([len(y_pred), len(y_pred[0]) * num_leaf], dtype=np.int64)
for i in range(0, len(y_pred)):
    temp = np.arange(len(y_pred[0])) * num_leaf + np.array(y_pred[I])
    transformed_testing_matrix[i][temp] += 1
  • LR训练 然后我们可以用转换后的训练集特征和label训练我们的LR模型,并对测试集进行测试:
1
2
3
lm = LogisticRegression(penalty='l2',C=0.05) # logestic model construction
lm.fit(transformed_training_matrix,y_train)  # fitting the data
y_pred_test = lm.predict_proba(transformed_testing_matrix)   # Give the probabilty on each label

我们这里得到的不是简单的类别,而是每个类别的概率

✅deepfm介绍

【通俗易懂】手把手带你实现DeepFM!

DeepFM 模型在解决特征交叉问题上非常有优势,它会使用一个独特的 FM 层来专门处理特征之间的交叉问题。

具体来说,就是使用点积、元素积等操作让不同特征之间进行两两组合,再把组合后的结果输入的输出神经元中,这会大大加强模型特征组合的能力。因此,DeepFM 模型相比于 Embedding MLP、Wide&Deep 等模型,往往具有更好的推荐效果。

img

  • DeepFM包含两部分:因子分解机部分神经网络部分,分别负责低阶特征的提取高阶特征的提取。这两部分共享同样的嵌入层输入

  • 嵌入层: 主要根据特征索引得到对应特征的embedding。

    通过嵌入层,尽管不同field的长度不同(不同离散变量的取值个数可能不同),但是embedding之后向量的长度均为K

  • FM: FM部分是一个因子分解机。因为引入了隐变量的原因,对于几乎不出现或者很少出现的隐变量,FM也可以很好的学习。FM的输出公式包含了线性项和二阶特征项: FM

  • 深度部分: 深度部分是一个多层前馈神经网络。其中使用relu作为网络的激活函数,输出层使用sigmoid作为激活函数。

  • DeepFM的预测结果可以写为:

    image-20210703231952611

image-20210703231615635

Embedding介绍

image-20210703232513030

嵌入层(embedding layer)的结构如上图所示。通过嵌入层,尽管不同field的长度不同(不同离散变量的取值个数可能不同),但是embedding之后向量的长度均为K(我们提前设定好的embedding-size)。通过代码可以发现,在得到embedding之后,我们还将对应的特征值乘到了embedding上,这主要是由于fm部分和dnn部分共享嵌入层作为输入,而fm中的二次项如下:🚩

image-20210703232532535

Vi,vj是embedding向量(内积),xj1·xj2是特征值,若:

  1. 类别向量: xji 是类别向量,xji取1
  2. 数值特征:按原来取值

DeepFM中,很重要的一项就是embedding操作,所以我们先来看看什么是embedding,可以简单的理解为,将一个特征转换为一个向量

在推荐系统当中,我们经常会遇到离散变量,如userid、itemid。对于离散变量,我们一般的做法是将其转换为one-hot,但对于itemid这种离散变量,转换成one-hot之后维度非常高,但里面只有一个是1,其余都为0。这种情况下,我们的通常做法就是将其转换为embedding

embedding的过程是什么样子的呢?它其实就是一层全连接的神经网络,如下图所示:

image-20210704092830904

假设一个离散变量共有5个取值,也就是说one-hot之后会变成5维,我们想将其转换为embedding表示,其实就是接入了一层全连接神经网络。由于只有一个位置是1,其余位置是0,因此得到的embedding就是与其相连的图中红线上的**权重**。

tf.nn.embedding_lookup函数介绍

在tf1.x中,我们使用embedding_lookup函数来实现embedding,代码如下:

1
get_embedding1 = tf.nn.embedding_lookup(embedding,feature_batch)

在embedding_lookup中,第一个参数相当于一个二维的词表,并根据第二个参数中指定的索引,去词表中寻找并返回对应的行。上面的过程为:

image-20210704151508258因此,使用embedding_lookup的话,我们不需要将数据转换为one-hot形式,只需要传入对应的feature的index即可。

数据处理

接下来进入代码实战部分。

首先我们来看看数据处理部分,通过刚才的讲解,想要给每一个特征对应一个k维的embedding,如果我们使用embedding_lookup的话,需要得到连续变量或者离散变量对应的特征索引feature index

可以看到,此时共有5个field,一个连续特征就对应一个field。

但是在FM的公式中,不光是embedding的内积,特征取值也同样需要。对于离散变量来说,特征取值就是1,对于连续变量来说,特征取值是其本身,因此,我们想要得到的数据格式如下:

image-20210704153224321

部分数据集如下:

image-20210704160322600

接下来,想要得到一个feature-map。这个**featrue-map**定义了如何将变量的不同取值转换为其对应的特征索引feature-index

定义了total_feature来得到总的特征数量,定义了feature_dict来得到变量取值到特征索引的对应关系,结果如下:

可以看到,对于连续变量,直接是变量名到索引的映射,对于离散变量,内部会嵌套一个二级map,这个二级map定义了该离散变量的不同取值到索引的映射。

下一步,需要将训练集和测试集转换为两个新的数组,分别是feature-index,将每一条数据转换为对应的特征索引,以及feature-value,将每一条数据转换为对应的特征值。

此时的训练集的特征索引:

image-20210704160448652

特征值:离散变量特征取值为1,连续变量特征取值为本身

模型参数及输入

接下来定义模型的一些参数,如:学习率(0.001)、embedding的大小(8)、深度网络的参数(Epoch:30 batch_size:1024 Optimizer:adam)、激活函数(tf.nn.relu)等等,还有两个比较重要的参数,分别是feature的大小和field的大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"""模型参数"""
dfm_params = {
    "use_fm":True,
    "use_deep":True,
    "embedding_size":8,
    "dropout_fm":[1.0,1.0],
    "deep_layers":[32,32],
    "dropout_deep":[0.5,0.5,0.5],
    "deep_layer_activation":tf.nn.relu,
    "epoch":30,
    "batch_size":1024,
    "learning_rate":0.001,
    "optimizer":"adam",
    "batch_norm":1,
    "batch_norm_decay":0.995,
    "l2_reg":0.01,
    "verbose":True,
    "eval_metric":'gini_norm',
    "random_seed":3
}
dfm_params['feature_size'] = total_feature
dfm_params['field_size'] = len(train_feature_index.columns)

训练模型的输入有三个,分别是刚才转换得到的特征索引和特征值,以及label

1
2
3
4
5
"""开始建立模型"""
feat_index = tf.placeholder(tf.int32,shape=[None,None],name='feat_index')
feat_value = tf.placeholder(tf.float32,shape=[None,None],name='feat_value')

label = tf.placeholder(tf.float32,shape=[None,1],name='label')

定义好输入之后,再定义一下模型中所需要的weights

介绍两个比较重要的参数:

  • weights[‘feature_embeddings’]是每个特征所对应的embedding,它的大小为feature-size * embedding-size,
  • weights[‘feature_bias’] ,这个是FM部分计算时所用到的一次项的权重参数,可以理解为embedding-size为1的embedding table,它的大小为feature-size * 1。

2.5 嵌入层

嵌入层,主要根据特征索引得到对应特征的embedding:

1
2
3
4
5
6
"""embedding"""
embeddings = tf.nn.embedding_lookup(weights['feature_embeddings'],feat_index)

reshaped_feat_value = tf.reshape(feat_value,shape=[-1,dfm_params['field_size'],1])

embeddings = tf.multiply(embeddings,reshaped_feat_value)

这里注意的是,在得到对应的embedding之后,还乘上了对应的特征值,这个主要是根据FM的公式得到的。过程表示如下:

image-20210704175531989

FM部分

我们先来回顾一下FM的公式,在传统的一阶线性回归之上,加了一个二次项,可以表达两两特征的相互关系。

FM

在传统的一阶线性回归之上,加了一个二次项,可以表达两两特征的相互关系。

特征相互关系

这里的公式可以简化,减少计算量,

FM

Deep部分

Deep部分很简单了,就是多层全连接网络。

输出部分

image-20210704194711291

🚩Deepfm的实现流程

  1. 导入包;读取数据;定义稀疏特征(类别特征)和稠密特征(连续特征);对空值进行处理,稀疏特征’-1’填充, 稠密特征用0填充。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd
from sklearn.preprocessing import LabelEncoder, MinMaxScaler
from sklearn.model_selection import train_test_split
from deepctr.models import DeepFM
from deepctr.feature_column import SparseFeat, DenseFeat,get_feature_names

data = pd.read_csv('./criteo_sample.txt') #读取数据

sparse_features = ['C' + str(i) for i in range(1, 27)] #稀疏特征一般是类别特征
dense_features = ['I'+str(i) for i in range(1, 14)]

data[sparse_features] = data[sparse_features].fillna('-1', ) # fillna是对空值的填充处理函数
data[dense_features] = data[dense_features].fillna(0,)
target = ['label'] 
  1. 数据预处理:神经网络输入的都是数字,因此需要对类别特征进行编码,LabelEncoder。
1
2
3
for feat in sparse_features:
    lbe = LabelEncoder()
    data[feat] = lbe.fit_transform(data[feat])
  1. 生成特征列:将类别特征通过嵌入技术将其转化成稠密向量,与稠密特征一起拼接,作为神经网络的输入向量。

稠密特征只有一个取值,疑问:稠密特征怎么计算embedding?答:在得到对应的embedding之后,还乘上了对应的特征值,这个主要是根据FM的公式得到的。对于类别特征乘的值是1,对于稠密特征来说需要乘的值是特征值50/100/…,所以对每个稠密特征相当于构造了一个1*k的embedding向量,只不过不同的取值需要对embedding向量乘特征值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 标签编码

fixlen_feature_columns = [SparseFeat(feat, vocabulary_size=data[feat].max() + 1,embedding_dim=4) # 设置embedding维度8
                       for i,feat in enumerate(sparse_features)] \ # 因为之前编码从0开始,所以特征值的个数为data[feat].max()+1
						+ [DenseFeat(feat, 1,) for feat in dense_features]  # 稠密特征只有一个取值,疑问:稠密特征怎么计算embedding?
# 或者
fixlen_feature_columns = [SparseFeat(feat, data[feat].nunique())
                          for feat in sparse_features] + [DenseFeat(feat, 1, ) for feat in dense_features]

#生成特征列
dnn_feature_columns = fixlen_feature_columns  #用做dnn的输入向量
linear_feature_columns = fixlen_feature_columns #用在线性模型的输入特征

#获取所有特征的名字
feature_names = get_feature_names(linear_feature_columns + dnn_feature_columns) 
  1. 生成训练样本和模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
train, test = train_test_split(data, test_size=0.2) #按照8:2的比例划分数据集为训练集合测试集

train_model_input = {name:train[name].values for name in feature_names} 
test_model_input = {name:test[name].values for name in feature_names}

model = DeepFM(linear_feature_columns, dnn_feature_columns, embedding_size=8,
               use_fm=True, dnn_hidden_units=(256, 256, 256), l2_reg_linear=0.001,
               l2_reg_embedding=0.001, l2_reg_dnn=0, init_std=0.0001, seed=1024,
              dnn_dropout=0.5, dnn_activation='relu', dnn_use_bn=True, task='binary') #调用deepctr库中的DeepFM模型,执行二分类任务

model.compile("adam", "binary_crossentropy",
              metrics=['accuracy', 'AUC'], ) #设置优化器,损失函数类型和评估指标

history = model.fit(train_model_input, train[target].values,
                    batch_size=256, epochs=10, verbose=2, validation_split=0.2, ) #fit数据
pred_ans = model.predict(test_model_input, batch_size=256) # 预测

deepfm面试问题

deepfm的优点有哪些

1、DeepFM(FM Component + Deep Component)包含两部分:因子分解机(FM)部分与(DNN)部分,FM部分负责低阶特征的提取(包括一阶和二阶,虽然FM也可以实现高于二阶的特征提取,但是由于计算复杂度的限制,一般只计算到二阶),DNN部分负责高阶特征的提取,这样可以避免人工构造复杂的特征工程。

2、共享feature embedding。FM和Deep共享输入和feature embedding不但使得训练更快,而且使得训练更加准确

与wide&deep区别

DeepFM:在Wide&Deep的基础上进行改进,

  1. 把wide模型部分的LR替换为FM,借助FM的显示交叉帮助DNN更好的完成Embedding。不需要预训练FM得到隐向量,不需要人工特征工程,能同时学习低阶和高阶的组合特征; 使用FM取代Wide部分的LR,这样可以避免人工构造复杂的特征工程。

  2. FM模块和Deep模块共享Feature Embedding部分,可以更快的训练,以及更精确的训练学习

Wide&Deep:同时学习低阶和高阶组合特征,它混合了一个线性模型(Wide part)和Deep模型(Deep part)。这两部分模型需要不同的输入,而Wide part部分的输入,依旧依赖人工特征工程。

wide&deep

不同输入

这是google paly 商店的推荐应用,wide模型和deep模型接受了不同的特征。 deep模型直接接收连续特征和embedding后的离散特征。其输出的即 a^{l}=W^{l}a^{l-1}+b^{l} wide模型只接受了部分离散特征:user installed app即用户安装的app,impression app即用户看到的被展示的app(看到了但没下载),以及这两部分特征的交叉。其输出即 [x,\phi(/img/in-post/20_07/math-20210827140521671)]其中

\phi(/img/in-post/20_07/math-20210827140521704) 是交叉特征。

wide与deep分别代表了什么?

wide是简单的线性模型,他会记住训练数据中已经出现的模式,并赋予权重。这代表了记忆 deep是深度的复杂模型,会在一层层的网络中计算出训练数据中未出现的模式的权重。这代表了泛化 这里的模式,可以简单理解为特征组合。

✅ Word2vec介绍

  • google 2013年提出的经典embedding方法,生成对词的向量表达

    用onehot表示,实际词表很大维度很高,所以需要适当的降维方式将向量稠密化。Word2vec 只关心模型训练完后的副产物——模型参数(这里特指神经网络的权重(输入向量)),并将这些参数,作为输入 x 的某种向量化的表示,这个向量便叫做——词向量。

  • 2种训练模型:Skip-Gram和CBOW。Skip-gram认为每个词决定了相邻的词,所以是由中心词预测周围词,CBOW认为每个词都由相邻的词决定,所以是由周围词预测中心词,一般是用周围词的求和平均预测中心词。

    • 代价函数就是可以最小化这两个矩阵的交叉熵。这里我们需要注意的是,隐层神经元是没有激活函数的,或者说采用了输入即输出的恒等函数作为激活函数,而输出层神经元采用了 softmax 作为激活函数。

    训练样本生成方式:选取滑窗大小,在一个句子中滑动一次即一条训练样本。

    因为词意主要由上下文决定,所以联系上下文确定出的词向量的含义更加准确。

  • 🚩 有2种加速训练的方法:层次softmax和负采样。

    • 层次softmax基本思想是:使用Huffman树的结构实现,把softmax转化为多次的sigmoid,复杂度由O(n)变为O(log(n))。

    • 负采样的基本思想是:把多分类问题转化为二分类问题,正样本是(一个中心词和一个周围词),负样本是(一个中心词和一个随机采样的负样本)

      负采样: 语料库词太多了,每次迭代更新权重时,都需要计算字典中所有词的预测误差。 负采样只需对采样出的几个负样本(一般小于10)计算预测误差。优化目标退化成一个近似二分类问题。https://zhuanlan.zhihu.com/p/52517050

  • 还有1种优化的技术是:重采样。

    • 重采样的基本思想是:把高频词根据频次进行删除,把低频次尽量的保留下,这样就可以加速训练,并且能够得到更好的词向量。
  • 模型复杂度上,每次计算所需要的参数

    • 几个结论 image-20210718161416778

      • cbow比skip快10倍

        主要是反向传播比较花费时间,cbow一共预测V(vocab size)次就够了,复杂度大概是O(V)。skip-gram是O(KV)

      • cbow和skip都比前馈和循环快

      • 负采样比层次softmax更快(14、15是临界)

  • 模型效果上

    • skip gram的训练时间更长,但是尤其对于一些出现频率不高的词,在CBOW中的学习效果就不如skipgram。反正mikolov自己说的skip准确率比CBOW高
  • 目标函数: 希望所有时间窗口内的样本条件概率之和最大。(基于极大似然估计) image-20210718123102349

    c是窗口大小, 利用梯度下降法则使负的目标函数最小

    • 条件概率的定义: 多分类用softmax函数, 同时用词之间内积距离表示语义的接近程度,所以定义为: image-20210718143534916

      uw是所有词的周围词的词向量

  • 输入: (doc id,words),得到word Embedding

  • 输入:(user id,itemids),得到item Embedding

有2 种训练模式 — Skip-gram

用当前词来预测上下文。

img

image-20210718144502995

image-20210718144602093

可以看成是 单个x->单个y 模型的并联,cost function 是单个 cost function 的累加(取log之后)

有2 种训练模式 — CBOW

通过上下文来预测当前值。

img

更 Skip-gram 的模型并联不同,这里是输入变成了多个单词,所以要对输入处理下(一般是求和然后平均),输出的 ==cost== function 不变

image-20210718144200004

image-20210718144413277

对比两种训练方式

  • 模型复杂度上,每次计算所需要的参数

    • skip-gram

      • 普通skip-gram参数个数: $Q=C(D+D*V)$

        对于每个中心词:中心词向量D,W’的参数:D*V,V是词编码维度,周围词个数C 对于每个中心词需要计算的参数的个数为:周围词个数C * (中心词向量维度D + 词向量维度D *词编码维度V)

      • 基于层次softmax 的skip-gram参数个数: $Q=C(D+D*log_2V)$

        log2V表示基于层次softmax,可以只计算log2V次sigmoid。

      • 基于负采样 的skip-gram参数个数: $Q=C(D+D*(K+1))$

        (1个正样本+K个负样本)*D维词向量

    • CBOW训练更快,理论上要快10倍

      • 普通: $Q=CD+DV$
      • 层次softmax:$Q=CD+Dlog_2V$
      • 负采样:$Q=CD+D(K+1)$
    • 几个结论 image-20210718161416778

      • cbow和skip都比前馈和循环快
      • cbow比skip快10倍
      • 负采样比层次softmax更快(14、15是临界)
  • 模型效果上

    • skip gram的训练时间更长,但是尤其对于一些出现频率不高的词,在CBOW中的学习效果就不如skipgram。反正mikolov自己说的skip准确率比CBOW高

      • 总结来说就是CBOW模型中input是context(周围词)而output是中心词,训练过程中其实是在从output的loss学习周围词的信息也就是embedding,但是在中间层是average的,一共预测V(vocab size)次就够了,复杂度大概是O(V);。

      • skipgram是用中心词预测周围词,预测的时候是一对word pair,等于对每一个中心词都有K个词作为output,对于一个词的预测有K次,所以能够更有效的从context中学习信息,但是总共预测K*V次。时间的复杂度为O(KV)

      • 🚩 但是在skip-gram当中,每个词都要收到周围的词的影响,每个词在作为中心词的时候,都要进行K次的预测、调整。因此, 当数据量较少,或者词为生僻词出现次数较少时, 这种多次的调整会使得词向量相对的更加准确。因为尽管cbow从另外一个角度来说,某个词也是会受到多次周围词的影响(多次将其包含在内的窗口移动),进行词向量的跳帧,但是他的调整是跟周围的词一起调整的,grad的值会平均分到该词上, 相当于该生僻词没有收到专门的训练,它只是沾了周围词的光而已

      • 从更通俗的角度来说:

        在skip-gram里面,每个词在作为中心词的时候,实际上是 1个学生 VS K个老师,K个老师(周围词)都会对学生(中心词)进行“专业”的训练,这样学生(中心词)的“能力”(向量结果)相对就会扎实(准确)一些,但是这样肯定会使用更长的时间;

        cbow是 1个老师 VS K个学生,K个学生(周围词)都会从老师(中心词)那里学习知识,但是老师(中心词)是一视同仁的,教给大家的一样的知识。至于你学到了多少,还要看下一轮(假如还在窗口内),或者以后的某一轮,你还有机会加入老师的课堂当中(再次出现作为周围词),跟着大家一起学习,然后进步一点。因此相对skip-gram,你的业务能力肯定没有人家强,但是对于整个训练营(训练过程)来说,这样肯定效率高,速度更快。

存在的问题

在进行最优化的求解过程中:从隐藏层到输出的Softmax层的计算量很大,因为要计算所有词的Softmax概率,再去找概率最大的值。

优化(加速)方法 — 层次softmax(hierarchical softmax)

本质是把 N 分类问题变成 log(N) 次二分类,所以复杂度也从O(n)变成了

把softmax转化为 V个sigmoid

构建huffman树(带权重的路径最短二叉树),只需计算少于log2(V)次的sigmoid,大幅度减少计算

优化(加速)方法 — 负采样(negative sampling)

本质是预测总体类别的一个子集。将多分类问题转化为2分类问题。

不同于原本每个训练样本更新所有的权重,负采样每次让一个训练样本仅仅更新一小部分的权重,这样就会降低梯度下降过程中的计算量。 当使用负采样时,我们将随机选择一小部分的negative words(比如选5个negative words)来更新对应的权重。我们也会对我们的“positive” word进行权重更新(在我们上面的例子中,这个单词指的是”quick“)。

一个单词被选作negative sample的概率跟它出现的频次有关,出现频次越高的单词越容易被选作negative words。每个单词被选为“negative words”的概率计算公式与其出现的频次有关, 代码中的公式实现如下:

[公式]

每个单词被赋予一个权重$f(w_i)$,它代表着单词出现的频次。

因为往往频次高的词不重要,所以要用3/4,使频率小的词概率大一点

优化方法3 — 高频词重采样

文档中的词,出现频率高的信息小,出现频率小的 信息多,比如方法tfidf、BM25就是根据这个思想实现的,在词袋模型里有比较好的效果。

原因:1. 希望更多地训练重要的词对(频率低) 2. 高频词很快就训练好了,低频次需训练很多轮次

方法:训练集中的词w会以 $P(w_i)$的概率被删除:

image-20210718153314845

词频越大,P越大;如果词频f小于t(如10**-5),则不会被删除

优点:加速训练,能够得到更好的词向量。

优缺点

优点:

  1. 由于 Word2vec 会考虑上下文,跟之前的 Embedding 方法相比,效果要更好(但不如 18 年之后的方法)
  2. 比之前的 Embedding方 法维度更少,所以速度更快
  3. 通用性很强,可以用在各种 NLP 任务中

缺点:

  1. 由于词和向量是一对一的关系,所以多义词的问题无法解决。
  2. Word2vec 是一种静态的方式,虽然通用性强,但是无法针对特定任务做动态优化

Word2vec面试问题

为什么不使用非线性激活函数?

输出层接softmax 函数,隐藏层一般线性激活函数。

  1. 最大好处是训练快,

  2. 更重要的原因是输入词和预测词,在语料库中往往有共现关系(相邻共同出现)的,用线性激活函数主要保留这层共现关系(训练出来的词大量线性相关🚩)。

item2vec介绍

  • word2vec在推荐系统中的推广,微软于2016年提出。
  • 用户embedding向量由历史行为中的物品embedding平均或聚类得到,计算用户向量与物品向量相似性直接在推荐系统召回层快速得到候选集和,或在排序层直接用于最终推荐列表排序。
  • 物品序列的产生: 由特点用户的浏览、购买等行为产生的历史行为记录序列。
  • 和word2vec唯一不同在于, 摒弃掉时间窗口概念, 认为序列中任意两个物品都相关。所以目标函数两两物品对数概率之和: image-20210718163933488
  • 广义的item2vec: 双塔模型, 物品侧+用户侧, 本质是接受物品和用户相关的特征向量, 经过多层神经网络结构生成多维的稠密向量,实则为embedding向量。只不过模型不是简单的w2v,而是更复杂灵活的模型,输入特征不止的one-hot,可以包含更多的特征向量。
  • 局限性:只能利用序列化数据,处理网络化数据不容易。需要Graph Embedding
  • 数据:
    • 输入: (doc id,words),得到word Embedding
    • 输入:(user id,itemids),得到item Embedding
    • 使用标题/内容的分词Embedding做推荐,属于内容相似性推荐
    • 使用行为列表作推荐,属于行为相关性推荐,效果比内容相似推荐好
    • 把word embedding 进行加和、平均,得到document embedding
    • 把item embedding 进行加和、平均,得到user embedding

评价指标

AUC

FM算法

FM算法解析

应用场景

点击预估。

准确的估计 ctr、cvr 对于提高流量的价值,增加广告收入有重要的指导作用。业界常用的方法有人工特征工程 + LR(Logistic Regression)、GBDT(Gradient Boosting Decision Tree) + LR[1][2][3]、FM(Factorization Machine)[2][7]和FFM(Field-aware Factorization Machine)[9]模型。在这些模型中,FM和FFM近年来表现突出,分别在由Criteo和Avazu举办的CTR预测竞赛中夺得冠军[4][5]

在进行CTR预估时,除了单特征外,往往要对特征进行组合。对于特征组合来说,业界现在通用的做法主要有两大类:FM系列Tree系列

目的

旨在解决大规模稀疏数据下的特征组合问题的机器学习模型。

优势

  • 高度稀疏数据场景;
  • 具有线性的计算复杂度。

DeepFM

新闻推荐系统项目 word2vec生成研报Embedding,怎么用的 什么原理

deepfm里的损失函数的改进—看论文

DNN 与 DeepFM 之间的区别

DNN 是 DeepFM 中的一个部分,DeepFM 多一次特征,多一个 FM 层的二次交叉特征

2.你在使用 deepFM 的时候是如何处理欠拟合和过拟合问题的

欠拟合:增加deep部分的层数,增加epoch的轮数,增加learningrate,减少正则 化力度

过拟合:在deep层直接增加dropout的率,减少epoch轮数,增加更多的数据,增 加正则化力度,shuffle 数据