🚩GBDT+LR

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

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

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

  1. 首先将数值型的连续特征作为lgb的输入,(假设100棵树,设置的叶子节点数num_leaves=64)然后进行特征转换。
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的预测结果,而是每一条训练数据落在了每棵树的哪个叶子结点上,因此需要使用下面的语句:

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

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

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

结果为:

(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]]

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


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
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
img

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,代码如下:

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的大小:

"""模型参数"""
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

"""开始建立模型"""
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

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

2.5 嵌入层

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

"""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

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

特征相互关系

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

FM
FM

Deep部分

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

输出部分

image-20210704194711291

🚩Deepfm的实现流程

  1. 导入包;读取数据;定义稀疏特征(类别特征)和稠密特征(连续特征);对空值进行处理,稀疏特征’-1’填充, 稠密特征用0填充。
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。
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向量乘特征值。

# 标签编码

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. 生成训练样本和模型
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,(/img/in-post/20_07/math-20210827140521671)]其中

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

wide与deep分别代表了什么?

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

✅ Word2vec介绍

有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

对比两种训练方式

存在的问题

在进行最优化的求解过程中:从隐藏层到输出的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(wi),它代表着单词出现的频次。

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

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

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

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

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

image-20210718153314845

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

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

优缺点

优点:

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

缺点:

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

Word2vec面试问题

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

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

  1. 最大好处是训练快,

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

item2vec介绍

评价指标

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 数据