全面的哈尔滨网站建设,天津企业seo,分销系统方案,智能建造的发展趋势原文地址#xff1a;FinalMLP: A Simple yet Powerful Two-Stream MLP Model for Recommendation Systems
了解 FinalMLP 如何转变在线推荐#xff1a;通过尖端 AI 研究解锁个性化体验
2024 年 2 月 14 日 介绍
世界正在向数字时代发展#xff0c;在这个时代#xff0c;…原文地址FinalMLP: A Simple yet Powerful Two-Stream MLP Model for Recommendation Systems
了解 FinalMLP 如何转变在线推荐通过尖端 AI 研究解锁个性化体验
2024 年 2 月 14 日 介绍
世界正在向数字时代发展在这个时代每个人都可以通过点击距离获得几乎所有他们想要的东西。可访问性、舒适性和大量的产品为消费者带来了新的挑战。我们如何帮助他们获得个性化的选择而不是在浩瀚的选择海洋中搜索?这就是推荐系统的用武之地。
推荐系统可以帮助组织增加交叉销售和长尾产品的销售并通过分析客户最喜欢什么来改进决策。不仅如此他们还可以学习过去的客户行为给定一组产品根据特定的客户偏好对它们进行排名。使用推荐系统的组织在竞争中领先一步因为它们提供了增强的客户体验。
在本文中我们将重点介绍FinalMLP这是一个旨在提高在线广告和推荐系统中点击率(CTR)预测的新模型。通过将两个多层感知器(MLP)网络与门控和交互聚合层等高级功能集成在一起FinalMLP优于传统的单流MLP模型和复杂的双流CTR模型。作者通过基准数据集和现实世界的在线A/B测试测试了它的有效性。
除了提供FinalMLP及其工作原理的详细视图外我们还提供了实现和将其应用于公共数据集的演练。我们在一个图书推荐设置中测试了它的准确性并评估了它解释预测的能力利用作者提出的两流架构。
FinalMLP: (F)特征门控层和(IN)交互层(A)聚合层(L)在两个mlp之上
FinalMLP[1]是建立在DualMLP[2]之上的两流多层感知器(MLP)模型通过引入两个新概念对其进行增强:
基于门控的特征选择增加了两个流之间的区别使每个流专注于从不同的特征集学习不同的模式。例如一个流侧重于处理用户特征而另一个流侧重于处理项目特征。多头双线性融合改进了两个流的输出如何通过建模特征交互进行组合。使用依赖于求和或串联等线性操作的传统方法可能不会发生这种情况。
Figure 2: FinalMLP architecture (source)
它是如何工作的?
如前所述FinalMLP是由两个简单并行的MLP网络组成的两流CTR模型从不同的角度学习特征交互它由以下关键组件组成:
特征embedding 层是一种将高维和稀疏的原始特征映射到密集数字表示的常用方法。无论是分类的、数值的还是多值的每个特征都被转换成一个embedding向量并连接起来然后再输入feature Selection模块。
将Categorical Features转换为单热特征向量并与词汇量n、embedding维数d的可学习embedding矩阵相乘生成其嵌入[3]。
数值特征可以通过以下方式转换为嵌入:1)将数值存储为离散特征然后将它们作为分类特征处理或者2)给定标准化标量值xjembedding可以作为xj与vj 的乘法 vj 是j域中所有特征的共享embedding向量[3].
多值 f特征可以表示为将值序列转换为长度为k的单热编码向量(k为序列的最大长度)然后乘以一个可学习的embedding矩阵来生成其嵌入[3]。 图3:每种特征类型的embedding创建(由作者提供的图像)
The feature Selection Layer用于获取特征重要性权重以抑制噪声特征使重要特征对模型预测的影响更大。
如前所述FinalMLP使用基于门控的特征选择这是一个带有门控机制的MLP。它接收嵌入作为输入并产生与输入相同维数的权重向量。特征重要性权重是通过对权重向量应用sigmoid函数然后对乘数2进行乘数生成一个范围为[0,2]的向量来获得的。然后通过特征embedding与特征重要性权重的逐元素积得到加权特征。
这个过程减少了两个流之间的同质学习使特征交互的学习更加互补。它被独立地应用于每个流以区分每个流的特征输入并允许它们专注于用户或项目维度。 图4使用独立MLP的特征选择过程每个流都有一个门控机制(图片来自作者)
流级融合层需要结合两个流的输出以获得最终的预测概率。通常组合两个输出依赖于求和或连接操作。然而FinalMLP的作者提出了一个双线性交互聚合层将两个输出结合起来从特征交互中获得线性组合可能无法获得的信息。
受transformers结构中的注意力层的启发作者将双线性融合扩展为多头双线性融合层。它用于降低计算复杂度和提高模型的可扩展性。
双线性融合方程包括: 方程1双线性融合方程
其中σ是一个s型函数b是偏置项01是一个流的输出。w1是要应用到o1的可学习权值o2是另一个流的输出w2是要应用到o2的可学习权值。最后w3为提取特征交互信息的双线性项中的可学习矩阵。如果将w3设置为零矩阵则退化为传统的串联融合。
双线性融合和多头双线性融合的区别在于它不是使用来自两个流的整个向量应用双线性融合而是将输出o1和o2chunks到k子空间中。在每个子空间中进行双线性融合聚合并馈送s型函数以产生最终概率。
图5:多头双线性融合的实践(图片来源:作者)
使用FinalMLP创建图书推荐模型
在本节中我们将在许可CC0:公共领域下的Kaggle的公共数据集上实现FinalMLP。该数据集包含有关用户、图书以及用户对图书的评分的信息。
该数据集由以下内容组成
User-ID —用户标识Location —以逗号分隔的字符串包含用户所在的城市、州和国家Age — 用户的年龄ISBN — 图书标识符Book-Rating — 用户对某本书的评分Book-Title — 书的标题Book-Author — 书的作者Year-of-Publication — 该书出版的年份Publisher — 出版这本书的编辑
我们将根据与每个用户的相关性对图书进行排名。之后我们使用归一化贴现累积增益(nDCG)将我们的排名与实际排名进行比较(根据用户给出的评级对图书进行排序)。
nDCG是一种评估推荐系统质量的度量标准它根据结果的相关性来衡量结果的排名。它考虑每个项目的相关性及其在结果列表中的位置排名越高重要性越大。nDCG的计算方法是将折扣累积收益(DCG)与理想DCG (iDCG)进行比较后者是将排名较低的物品的收益进行折扣而理想DCG是给出完美排名的最高可能DCG。这个标准化分数的范围在0到1之间其中1表示理想的排名顺序。因此nDCG为我们提供了一种理解系统如何有效地向用户呈现相关信息的方法。
我们从导入库开始: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21%matplotlib inline
%load_ext autoreload
%autoreload 2
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import random
from sklearn.metrics import ndcg_score
from sklearn.decomposition import PCA
from sentence_transformers import SentenceTransformer
import os
import logging
from fuxictr.utils import load_config, set_logger, print_to_json
from fuxictr.features import FeatureMap
from fuxictr.pytorch.torch_utils import seed_everything
from fuxictr.pytorch.dataloaders import H5DataLoader
from fuxictr.preprocess import FeatureProcessor, build_dataset
import src
import gc
import os然后我们加载三个数据集并将它们合并为一个数据集: 1
2
3
4
5
6books_df pd.read_csv(data/book/Books.csv)
users_df pd.read_csv(data/book/Users.csv)
ratings_df pd.read_csv(data/book/Ratings.csv)df pd.merge(users_df, ratings_df, onUser-ID, howleft)
df pd.merge(df, books_df, onISBN, howleft)之后我们进行一些探索性数据分析以识别数据中的问题:
删除用户对书没有评价的地方。 1df df[df[Book-Rating].notnull()]检查缺失的值并将缺失的Book-Author和Publisher替换为unknown类别。 1
2
3
4print(df.columns[df.isna().any()].tolist())df[Book-Author] df[Book-Author].fillna(unknown)
df[Publisher] df[Publisher].fillna(unknown)删除书中缺失信息的评论。 1df df[df[Book-Title].notnull()]将非整数’出版年份’替换为空值。 1df[Year-Of-Publication] pd.to_numeric(df[Year-Of-Publication], errorscoerce)检查“年龄”、“出版年份”和“图书评级”分布以识别异常情况。 1
2
3
4
5
6
7
8
9
10
11plt.rcParams[figure.figsize] (20,3)
sns.histplot(datadf, xAge)
plt.title(Age Distribution)
plt.show()sns.histplot(datadf, xYear-Of-Publication)
plt.title(Year-Of-Publication Distribution)
plt.show()
sns.histplot(datadf, xBook-Rating)
plt.title(Book-Rating Distribution)
plt.show()图6:数据分布(作者图片)
最后我们通过以下方式清理数据:
替换age 100 (这似乎是一个错误)丢失的值稍后处理。将上限限制为2021年因为这是数据集在Kaggle中发布的时间并将Year-of-Publication 0 替换为稍后处理的缺失值。删除rating 0的观察因为这些书被用户阅读但没有评级。从“位置”创建3个新功能(“城市”“州”“国家”)。既然城市太吵了我们就不去了。为FinalMLP创建一个二进制标签我们考虑与用户相关的率高于7的书籍。 1
2
3
4
5
6
7
8df[Age] np.where(df[Age] 100, None, df[Age])df[Year-Of-Publication] np.where(df[Year-Of-Publication].clip(0, 2021) 0, None, df[Year-Of-Publication])
df df[df[Book-Rating] 0]
df[city] df[Location].apply(lambda x: x.split(,)[0].strip()) # too noisy, we will not use
df[state] df[Location].apply(lambda x: x.split(,)[1].strip())
df[country] df[Location].apply(lambda x: x.split(,)[2].strip())
df[label] (df[Book-Rating] 7)*1清理数据集后我们将数据分为训练、验证和测试随机选择70%的用户进行训练10%的用户进行验证20%的用户进行测试。 1
2
3
4
5
6
7
8
9
10
11
12
13# create list with unique users
users df[User-ID].unique()# shuffle list
random.shuffle(users)
# create list of users to train, to validate and to test
train_users users[:int(0.7*len(users))]
val_users users[int(0.7*len(users)):int(0.8*len(users))]
test_users users[int(0.8*len(users)):]
# train, val and test df
train_df df[df[User-ID].isin(train_users)]
val_df df[df[User-ID].isin(val_users)]
test_df df[df[User-ID].isin(test_users)]在将数据输入模型之前我们将对数据进行一些预处理:
我们使用多语言编码器为文本特征Book-Title创建嵌入并使用PCA降低维度解释了80%的方差。
我们使用多语言编码器因为标题是用不同的语言编写的。请注意我们首先提取不同的Book-title以便在用户多于另一本书时不影响降维。 1
2
3
4
5
6
7
8
9
10
11
12
13# create embeddings
train_embeddings utils.create_embeddings(train_df.copy(), Book-Title)
val_embeddings utils.create_embeddings(val_df.copy(), Book-Title)
test_embeddings utils.create_embeddings(test_df.copy(), Book-Title)# reduce dimensionality with PCA
train_embeddings, pca utils.reduce_dimensionality(train_embeddings, 0.8)
val_embeddings pca.transform(val_embeddings)
test_embeddings pca.transform(test_embeddings)
# join embeddings to dataframes
train_df utils.add_embeddings_to_df(train_df, train_embeddings, Book-Title)
val_df utils.add_embeddings_to_df(val_df, val_embeddings, Book-Title)
test_df utils.add_embeddings_to_df(test_df, test_embeddings, Book-Title)我们用中值填充数值特征的缺失值并使用MinMaxScaler对数据进行归一化。 1
2
3
4
5
6
7
8# set numerical columns
NUMERICAL_COLUMNS [i for i in train_df.columns if Book-Title_ in i] [Age, Year-Of-Publication]# define preprocessing pipeline and transform data
pipe utils.define_pipeline(NUMERICAL_COLUMNS)
train_df[NUMERICAL_COLUMNS] pipe.fit_transform(train_df[NUMERICAL_COLUMNS])
val_df[NUMERICAL_COLUMNS] pipe.transform(val_df[NUMERICAL_COLUMNS])
test_df[NUMERICAL_COLUMNS] pipe.transform(test_df[NUMERICAL_COLUMNS])准备好提供给FinalMLP的所有数据后我们必须创建两个yaml配置文件:dataset_config.yaml和model_config.yaml。
dataset_config.yaml负责定义模型中使用的特性。此外它还定义了它们的数据类型(它们在embedding层中的处理方式不同)以及到训练集、验证集和测试集的路径。你可以看到配置文件的主要部分如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18FinalMLP_book:data_root: ./data/book/feature_cols:- active: truedtype: floatname: [Age, Book-Title_0, Book-Title_1, Book-Title_2, Book-Title_3, Book-Title_4, Book-Title_5, Book-Title_6, Book-Title_7,Book-Title_8, ...]type: numeric- active: truedtype: strname: [Book-Author, Year-Of-Publication, Publisher, state, country]type: categoricalfill_na: unknownlabel_col: {dtype: float, name: label}min_categr_count: 1test_data: ./data/book/test.csvtrain_data: ./data/book/train.csvvalid_data: ./data/book/valid.csvmodel_config.yaml负责设置模型的超参数。您还必须定义哪些流将处理用户特性哪些流将处理项目特性。该文件应定义如下: 1
2
3
4
5
6FinalMLP_book:dataset_id: FinalMLP_bookfs1_context: [Age, state, country]fs2_context: [Book-Author, Year-Of-Publication, Publisher, Book-Title_0, Book-Title_1, Book-Title_2, Book-Title_3,Book-Title_4, Book-Title_5, ...]model_root: ./checkpoints/FinalMLP_book/我们回到Python并加载最近创建的配置文件。然后我们创建特征映射(即每个分类特征中有多少个类别如果存在它应该如何替换不同特征中的缺失值等等)。我们将csv转换为h5文件。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# Get model and dataset configurations
experiment_id FinalMLP_book
params load_config(fconfig/{experiment_id}/, experiment_id)
params[gpu] -1 # cpu
set_logger(params)
logging.info(Params: print_to_json(params))
seed_everything(seedparams[seed])# Create Feature Mapping and convert data into h5 format
data_dir os.path.join(params[data_root], params[dataset_id])
feature_map_json os.path.join(data_dir, feature_map.json)
if params[data_format] csv:# Build feature_map and transform h5 datafeature_encoder FeatureProcessor(**params)params[train_data], params[valid_data], params[test_data] \\build_dataset(feature_encoder, **params)
feature_map FeatureMap(params[dataset_id], data_dir)
feature_map.load(feature_map_json, params)
logging.info(Feature specs: print_to_json(feature_map.features))之后我们就可以开始模型的训练过程了。 1
2
3
4
5
6model_class getattr(src, params[model])
model model_class(feature_map, **params)
model.count_parameters() # print number of parameters used in modeltrain_gen, valid_gen H5DataLoader(feature_map, stagetrain, **params).make_iterator()
model.fit(train_gen, validation_datavalid_gen, **params)最后我们可以预测看不见的数据;我们只需要将批大小更改为1就可以对所有观察值进行评分。 1
2
3
4# to score all observations
params[batch_size] 1
test_gen H5DataLoader(feature_map, stagetest, **params).make_iterator()
test_df[score] model.predict(test_gen)我们选择了一个有不止一本书评级的客户并且每本书都有不同的评级以实现没有关系的适当排名。它的nDCG分数是0.986362因为我们放错了2本书距离为1位。
我们还使用Recall来评估FinalMLP。召回率是一个度量系统从一个集合中识别所有相关项目的能力的度量表示为从可用的所有相关项目中检索到的相关项目的百分比。当我们指定RecallK(比如Recall3)时我们关注的是系统在前K个推荐中识别相关项目的能力。这种适应对于评估推荐系统至关重要因为用户主要关注的是最热门的推荐。K的选择(例如3)取决于典型的用户行为和应用程序的上下文。
如果我们看一下这个客户的Recall3我们有100%因为另外三本相关的书被放在了前3个位置。 1
2
3
4true_relevance np.asarray([test_df[test_df[User-ID] 1113][Book-Rating].tolist()])
y_relevance np.asarray([test_df[test_df[User-ID] 1113][score].tolist()])ndcg_score(true_relevance, y_relevance)我们还计算了剩余测试集的nDCG分数并将FinalMLP性能与CatBoost Ranker进行了比较如图7所示。虽然这两个模型都表现得很好但FinalMLP在这个测试集上的表现略好每个用户的平均nDCG为0.963298而CatBoost Ranker *only*达到了0.959977。 图7:CatBoost Ranker与FinalMLP的nDCG对比(图片来源:作者)
在可解释性方面该模型执行特征选择使我们能够提取权重向量。然而解释和理解每个特性的重要性并不简单。请注意在embedding层之后我们最终得到了一个930维的向量这使得将其映射回原始特征变得更加困难。尽管如此我们可以尝试通过提取每个流的输出经过线性处理后的绝对值来理解每个流的重要性这些线性处理由前面提到的线性项给出如公式2所示。 方程2:线性项
为此我们需要更改 InteractionAggregation模块并添加以下代码行来提取每一步后的线性转换值: 1
2
3
4
5
6
7... self.x_importance []self.y_importance []def forward(self, x, y):self.x_importance.append(torch.sum(torch.abs(self.w_x(x))))self.y_importance.append(torch.sum(torch.abs(self.w_y(y))))
...经过训练后我们可以预测并绘制出每个流的线性变换所产生的绝对值。如图8所示项目流比用户流更重要。之所以会出现这种情况是因为我们有更多关于物品的功能但也因为用户功能非常普遍。 图8:用户流和项目流的重要性对比(作者图片)
结论
推荐系统通过提供个性化推荐和授权组织做出驱动增长和创新的数据驱动决策来增强用户体验。
在本文中我们介绍了为推荐系统开发的最新模型之一。最后mlp是一个具有两个独立网络的深度学习模型。每个网络都将其学习集中在两个不同视角中的一个:用户和项目。然后将从每个网络学习到的不同模式馈送到一个融合层该融合层负责将每个网络的学习结合起来。它创建用户-项目对交互的单一视图以生成最终分数。该模型在我们的用例中表现良好击败了CatBoost Ranker。
请注意算法的选择可能取决于您要解决的问题和您的数据集。对几种方法进行相互比较测试总是好的做法。您还可以考虑测试xDeepFM、AutoInt、hen或DLRM。
参考文献
[1] Kelong Mao, Jieming Zhu, Liangcai Su, Guohao Cai, Yuru Li, Zhenhua Dong. FinalMLP: An Enhanced Two-Stream MLP Model for CTR Prediction. arXiv:2304.00902, 2023.
[2] Jiajun Fei, Ziyu Zhu, Wenlei Liu, Zhidong Deng, Mingyang Li, Huanjun Deng, Shuo Zhang. DuMLP-Pin: A Dual-MLP-dot-product Permutation-invariant Network for Set Feature Extraction. arXiv:2203.04007, 2022.
[3] Jieming Zhu, Jinyang Liu, Shuai Yang, Qi Zhang, Xiuqiang He. BARS-CTR: Open Benchmarking for Click-Through Rate Prediction. arXiv:2009.05794, 2020.